开发者

string.Format fails at runtime with array of integers

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 开发者_JAVA技巧the compiler catch this?


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[].


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


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}


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


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


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜