string.Format не работает во время выполнения с массивом целых чисел

Рассмотрим string.Format(), параметры которого являются строкой и, среди прочего, в списке перегрузок, object[] или несколько объектов.

Это утверждение успешно:

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

как и это:

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

как и массив строк:

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

Кажется, что целые числа, если они указаны индивидуально, могут быть упакованы или приведены к типу object, который, в свою очередь, приводится к строке.

Этот оператор завершается ошибкой во время выполнения.

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

Индекс (отсчитываемый от нуля) должен быть больше или равен нулю и меньше размера списка аргументов.

  • Почему массив int не может или не может быть приведен или упакован в object[] или string[]?
  • Из небольшого любопытства, почему компилятор этого не улавливает?

person p.campbell    schedule 15.07.2010    source источник
comment
Наконец нашел удовлетворительное решение этой проблемы, которым я рад поделиться... пожалуйста, проверьте его.   -  person Matt    schedule 08.05.2018


Ответы (7)


Вызов завершается неудачно по той же причине, что и следующее:

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

Вы указываете два аргумента в format, но указываете только один объект.

Компилятор не улавливает его, потому что int[] передается как объект, который является вполне допустимым аргументом для функции.

Также обратите внимание, что ковариация массива не работает с типами значений, поэтому вы не можете:

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

Однако вы можете обойтись без:

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

что будет работать, потому что вы будете использовать перегрузку String.Format, которая принимает object[].

person João Angelo    schedule 15.07.2010
comment
ао: спасибо за этот ответ. Я обновил вопрос, чтобы включить успех string.Format() с string[]. Это чем-то отличается от int[]? - person p.campbell; 15.07.2010
comment
@ p.campbell, см. мое обновление о том, что ковариация массива не поддерживается в типах значений. - person João Angelo; 15.07.2010
comment
Итак, вы говорите, что int[] передается как объект, а не как массив или массив объектов, но object[] будет передаваться как массив? - person Jack; 29.03.2013
comment
@Jack, массив типов значений будет использовать перегрузку, которая принимает object, в то время как массив ссылочных типов будет использовать перегрузку, которая принимает object[]. - person João Angelo; 29.03.2013

Ваш звонок переводится в это:

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

что приводит к этой строке:

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

Так как {1} не имеет параметра, он выдает исключение

person Michael Stum    schedule 15.07.2010

Я думаю, что концепция, с которой у вас возникла проблема, заключается в том, почему int[] не преобразуется в object[]. Вот пример, который показывает, почему это было бы плохо

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

Проблема в том, что мы только что добавили объект в массив int.

Итак, что происходит в вашем коде, так это то, что myInts приводится к object, и у вас нет второго аргумента для заполнения {1}

person juharr    schedule 15.07.2010

Короткий способ заставить его работать (хотя и не самый оптимальный):

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);
person frost    schedule 11.03.2019

Это довольно старый вопрос, но недавно у меня возникла такая же проблема. И я не видел ответа, который работает для меня, поэтому я поделюсь найденным решением.

  • Почему массив int не может или не может быть приведен или упакован в объект[] или строку[]?
    Почему он не упакован, я не знаю. Но его можно явно упаковать, см. решение ниже.
  • Почему компилятор этого не улавливает?
    Потому что компилятор неправильно интерпретирует ситуацию: тип не совсем массив объектов, поэтому он не знает, что с ним делать, и решает для выполнения .ToString() над массивом int, который возвращает один единственный параметр, содержащий имя типа, а не сам список параметров. Он не делает этого со строковым массивом, потому что целевой тип уже является строкой, но с любым другим типом массива возникает та же проблема (например, bool[]).
    Рассмотрите var arr1 = new int[]{1,2}; с string.Format("{0}", arr1): Пока у вас есть только {0} в строке формата, вы возвращаете только имя типа "System.Int32[]" (и никаких исключений не возникает).
    Если у вас больше заполнителей, например. string.Format("{0}{1}", arr1), то возникает исключение, потому что arr1 неправильно интерпретируется как один параметр, а для компилятора второйй один отсутствует.
    Но то, что я считаю концептуальной ошибкой, заключается в том, что вы не можете преобразовать arr1, т. е. если вы попытаетесь сделать (object[])arr1-, вы получите:
    #P2#

Решение:

Заполнение каждого элемента массива int не является решением, которое работает для меня, потому что в моем проекте я динамически создаю строку шаблона формата во время выполнения, содержащую {0}...{n}, поэтому мне нужно передать массив в String.Format.

Поэтому я нашел следующий обходной путь. Я создал общую вспомогательную функцию (которая, конечно, может быть и методом расширения, если вы предпочитаете):

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

Теперь, если вы попробуете это в приведенном ниже примере, который показывает 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();

Исправлено: используйте Convert(arr1) в качестве 2nd параметра для string.Format(...), как показано ниже:

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

Попробуйте пример как DotNetFiddle

Вывод: кажется, что среда выполнения .NET действительно неправильно интерпретирует параметр, применяя к нему .ToString(), если он еще не имеет тип object[]. Метод Convert не дает среде выполнения другого выбора, кроме как сделать это правильно, потому что он возвращает ожидаемый тип. Я обнаружил, что явное преобразование типов не работает, поэтому понадобилась вспомогательная функция.

Примечание. Если вы вызываете метод много раз в цикле и вас беспокоит скорость, вы также можете преобразовать все в массив строк, что, вероятно, наиболее эффективно:

// 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;
}

Это тоже работает. Чтобы преобразовать из другого типа данных, например из словаря, вы можете просто использовать

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

Обновление: с интерполяцией строк есть еще один короткий способ решить эту проблему:

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

Ваш string.Format ожидает 2 аргумента ({0} и {1}). Вы предоставляете только 1 аргумент (int[]). Вам нужно что-то еще вроде этого:

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

Компилятор не замечает проблемы, потому что строка формата оценивается во время выполнения. IE Компилятор не знает, что {0} и {1} означают, что должно быть 2 аргумента.

person msergeant    schedule 15.07.2010

Это работает:

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

Компилятор не улавливает его, потому что он не оценивает вашу строку формата.

Пример, который вы дали сверху, не соответствует тому, что вы пытаетесь сделать внизу... вы предоставили два {} и два аргумента, но в нижнем вы предоставили только один аргумент.

person Fosco    schedule 15.07.2010