22

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?
1
  • Finally found a satisfying solution for this issue, which I am happy to share ... please check it out. Commented May 8, 2018 at 15:03

7 Answers 7

25

The call fails with the same reason the following will also fail:

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

You are specifying two arguments in the format but only specifying one object.

The compiler does not catch it because int[] is passed as an object which is a perfectly valid argument for the function.

Also note that array covariance does not work with value types so you cannot do:

object[] myInts = new int[] {8,9};

However you can get away with:

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

which would work because you would be using the String.Format overload that accepts an object[].

Sign up to request clarification or add additional context in comments.

4 Comments

ão: thanks for this answer. I've updated teh question to include the success of string.Format() with a string[]. Is that any different from the int[]?
@p.campbell, see my update about array covariance not being supported in value types.
So you are saying that int[] is being passed as an object instead of as an array or object array but object[] will be passed as an array?
@Jack, a value type array would use the overload that accepts an object while a reference type array would use the overload that accepts an object[].
3

Your call gets translated into this:

string foo = string.Format("{0} {1}", myInts.ToString());

which results in this string:

string foo = "System.Int32[] {1}";

So as the {1} doesn't have a parameter, it throws an exception

Comments

2

I think the concept you are having an issue with is why int[] isn't cast to object[]. Here's an example that shows why that would be bad

int[] myInts = new int[]{8,9};
object[] myObjs = (object[])myInts;
myObjs[0] = new object();

The problem is that we just added an object into a int array.

So what happens in your code is that myInts is cast to object and you don't have a second argument to fill in the {1}

Comments

1

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);

Comments

1

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());

Comments

0

Your string.Format is expecting 2 arguments ({0} and {1}). You are only supplying 1 argument (the int[]). You need something more like this:

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

The compiler does not notice the problem because the format string is evaluated at runtime. IE The compiler doesn't know that {0} and {1} mean there should be 2 arguments.

Comments

0

This works:

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

The compiler doesn't catch it because it doesn't evaluate your format string.

The example you gave up top doesn't match what you're trying to do down below... you provided two {} and two arguments, but in the bottom one you only provided one argument.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.