string.Format fails at runtime with array of integers

前端 未结 7 522
囚心锁ツ
囚心锁ツ 2020-12-31 00:03

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

This statement

7条回答
  •  被撕碎了的回忆
    2020-12-31 00:43

    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[] arr)
    {
        var obj = new List();
        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[] 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(Dictionary coll)
    {
        return ConvertStr(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());
    

    提交回复
    热议问题