string.Format fails at runtime with array of integers

Viewed 5896

Consider string.Format() whose parameters are a string and, among others in the overload list, an object[] or many objects.

This statement succeeds:

string foo = string.Format("{0} {1}", 5, 6);

as does this:

object[] myObjs = new object[] {8,9};
string baz = string.Format("{0} and {1}", myObjs;

as does an array of strings:

string[] myStrings = new string[] {"abc", "xyz"};
string baz = string.Format("{0} {1}", myStrings);

It seems that the integers, when specified individually, can be boxed or coerced to type object, which in turn is coerced to a string.

This statement fails at runtime.

int[] myInts = new int[] {8,9};
string bar = string.Format("{0} and {1}", myInts);

Index (zero based) must be greater than or equal to zero and less than the size of the argument list.

  • Why doesn't or can't the int array be coerced or boxed to an object[] or string[]?
  • Out of a small bit of curiosity, why doesn't the compiler catch this?
7 Answers

This is quite an old question, but I recently got the same issue. And I haven't seen an answer that works for me, so I'll share the solution I found.

  • Why doesn't or can't the int array be coerced or boxed to an object[] or string[]?
    Why it isn't boxed, I don't know. But it can be boxed explicitly, see solution below.
  • Why doesn't the compiler catch this?
    Because the compiler misinterprets the situation: The type isn't exactly an object array, so it doesn't know what to do with it and decides to perform a .ToString() on the int array, which returns one single parameter containing the type name rather than the parameter list itself. It doesn't do that with a string array, because the target type is already a string - but with any other kind of array the same issue happens (for example bool[]).
    Consider var arr1 = new int[]{1,2}; with string.Format("{0}", arr1): As long as you have only {0} in the format string, you get only the type name "System.Int32[]" back (and no exception occurs).
    If you have more placeholders, e.g. string.Format("{0}{1}", arr1), then the exception occurs - because arr1 is misinterpreted as one parameter - and for the compiler, a 2nd one is missing.
    But what I think is a conceptional bug is that you can't convert arr1, i.e. if you try to do (object[])arr1- you're getting:

    CS0030 Cannot convert type 'int[]' to 'object[]'

Solution:

Filling in each element of the int array is not a solution that works for me, because in my project I am creating a format template string dynamically during runtime containing the {0}...{n} - hence I need to pass an array to String.Format.

So I found the following workaround. I created a generic helper function (which of course could be an extension method too if you prefer):

// converts any array to object[] and avoids FormatException
object[] Convert<T>(T[] arr)
{
    var obj = new List<object>();
    foreach (var item in arr)
    {
        obj.Add((object)item);
    }   
    return obj.ToArray();
}

Now if you try that in the example below which is showing up the FormatException:

// FormatException: Index (zero based) must be greater than or equal to zero 
//                  and less than the size of the argument list
var arr1 = (new int[] { 1, 2 });
string.Format("{0}{1}{0}{1}", arr1).Dump();

Fix: Use   Convert(arr1)   as 2nd parameter for   string.Format(...)   as shown below:

// Workaround: This shows 1212, as expected
var arr1 = (new int[] { 1, 2 });
string.Format("{0}{1}{0}{1}", Convert(arr1)).Dump();

Try example as DotNetFiddle

Conclusion: As it seems, the .NET runtime really misinterprets the parameter by applying a .ToString() to it, if it is not already of type object[]. The Convert method gives the runtime no other choice than to do it the right way, because it returns the expected type. I found that an explicit type conversion did not work, hence the helper function was needed.

Note: If you invoke the method many times in a loop and you're concerned about speed, you could also convert everything to a string array which is probably most efficient:

// converts any array to string[] and avoids FormatException
string[] ConvertStr<T>(T[] arr)
{
    var strArr = new string[arr.Length];
    for (int i = 0; i < arr.Length; i++)
    {
        strArr[i]=arr[i].ToString();
    }
    return strArr;
}

This is working as well. To convert from a different datatype, such as a dictionary, you can simply use

string[] Convert<K,V>(Dictionary<K,V> coll)
{
    return ConvertStr<V>(coll.Values.ToArray());
}

Update: With string interpolation, another short way to solve it is:

var baz = string.Format("{0} and {1}", myInts.Select(s => $"{s}").ToArray());

Short way to make it work (not the most optimal though):

int[] myInts = new int[] { 8, 9 };
string[] myStrings = Array.ConvertAll(myInts, x => x.ToString());
// or using LINQ
// string[] myStrings = myInts.Select(x => x.ToString()).ToArray();
bar = string.Format("{0} and {1}", myStrings);
Related