Когда лучше использовать String.Format vs string concatenation?
У меня есть небольшой фрагмент кода, который анализирует значение индекса для определения ввода ячейки в Excel. Это заставило меня задуматься...
Какая разница между
xlsSheet.Write("C" + rowIndex.ToString(), null, title);
и
xlsSheet.Write(string.Format("C{0}", rowIndex), null, title);
Является ли "лучше" чем другой? И почему?
Ответы
Ответ 1
До С# 6
Честно говоря, я думаю, что первая версия проще - хотя я бы упростил ее:
xlsSheet.Write("C" + rowIndex, null, title);
Я подозреваю, что другие ответы могут говорить о хите производительности, но, честно говоря, он будет минимальным, если он вообще присутствует, и эта версия конкатенации не нуждается в анализе строки формата.
Строки формата отлично подходят для локализации и т.д., но в таком случае, как эта конкатенация, проще и работает так же хорошо.
С С# 6
Интерполяция строк позволяет упростить чтение на С# 6. В этом случае ваш второй код будет выглядеть следующим образом:
xlsSheet.Write($"C{rowIndex}", null, title);
который, вероятно, является лучшим вариантом, IMO.
Ответ 2
Мое начальное предпочтение (исходящее из фона С++) предназначалось для String.Format. Я отказался от этого позже по следующим причинам:
- Конкатенация строк может быть "более безопасной". Случилось со мной (и я видел, как это случилось с несколькими другими разработчиками), чтобы удалить параметр или испортить порядок параметров по ошибке. Компилятор не будет проверять параметры на строку формата, и в итоге вы получите ошибку времени выполнения (то есть, если вам посчастливилось не использовать ее в неясном методе, например, при регистрации ошибки). При конкатенации удаление параметра менее подвержено ошибкам. Вы можете утверждать, что вероятность ошибки очень мала, но она может.
- Конкатенация строк допускает нулевые значения, String.Format
- нет. Запись "s1 + null + s2
" не прерывается, она просто обрабатывает нулевое значение как String.Empty. Ну, это может зависеть от вашего конкретного сценария - бывают случаи, когда вам нужна ошибка вместо молчащего игнорирования null FirstName. Однако даже в этой ситуации я лично предпочитаю сначала проверять нули и бросать определенные ошибки вместо стандартного ArgumentNullException, получаемого из String.Format.Дел >
- Конкатенация строк выполняется лучше. Некоторые из вышеперечисленных сообщений уже упоминают об этом (без объяснения причин, почему я решил написать этот пост:).
Идея - это компилятор .NET, достаточно умный, чтобы преобразовать этот фрагмент кода:
public static string Test(string s1, int i2, int i3, int i4,
string s5, string s6, float f7, float f8)
{
return s1 + " " + i2 + i3 + i4 + " ddd " + s5 + s6 + f7 + f8;
}
:
public static string Test(string s1, int i2, int i3, int i4,
string s5, string s6, float f7, float f8)
{
return string.Concat(new object[] { s1, " ", i2, i3, i4,
" ddd ", s5, s6, f7, f8 });
}
Что происходит под капотом String.Concat легко угадать (используйте Reflector). Объекты в массиве преобразуются в свою строку через ToString(). Затем вычисляется общая длина и выделяется только одна строка (с общей длиной). Наконец, каждая строка копируется в результирующую строку через wstrcpy в некотором небезопасном фрагменте кода.
Причины String.Concat
быстрее? Ну, мы все можем посмотреть, что делает String.Format
- вы будете удивлены количеством кода, необходимого для обработки строки формата. Вдобавок к этому (я видел комментарии относительно потребления памяти), String.Format
использует StringBuilder внутренне. Вот как:
StringBuilder builder = new StringBuilder(format.Length + (args.Length * 8));
Итак, для каждого переданного аргумента он сохраняет 8 символов. Если аргумент является одноразрядным значением, то это слишком плохо, у нас есть какое-то потраченное впустую пространство. Если аргумент представляет собой пользовательский объект, возвращающий некоторый длинный текст на ToString()
, тогда может потребоваться даже некоторое перераспределение (конечно, сценарий наихудшего варианта).
По сравнению с этим конкатенация только уничтожает пространство массива объектов (не слишком много, принимая во внимание массив ссылок). Там нет разбора для спецификаторов формата и никакого промежуточного StringBuilder. Накладные расходы бокса/распаковки присутствуют в обоих методах.
Единственная причина, по которой я буду обращаться к String.Format, - это когда задействована локализация. Ввод строк формата в ресурсы позволяет поддерживать разные языки без использования кода (подумайте о сценариях, в которых форматированные значения изменяют порядок в зависимости от языка, то есть "после {0} часов и {1} минут" могут выглядеть совсем по-японски:).
Подводя итоги первого (и довольно длинного) сообщения:
- лучший способ (с точки зрения производительности и ремонтопригодности/удобочитаемости) для меня - использование конкатенации строк без каких-либо вызовов
ToString()
- если вы после исполнения, сделайте
ToString()
, чтобы избежать бокса (я немного предвзято к читабельности) - то же самое, что и первый вариант в вашем вопросе
- Если вы показываете локализованные строки пользователю (не здесь),
String.Format()
имеет ребро.
Ответ 3
Я думаю, что первый вариант более читабельен, и это должно быть вашей главной задачей.
xlsSheet.Write("C" + rowIndex.ToString(), null, title);
string.Format использует StringBuilder под капотом (проверьте reflector), чтобы он не имел каких-либо преимуществ в производительности, если только вы делают значительную часть конкатенации. Это будет медленнее для вашего сценария, но на самом деле это решение по оптимизации микропроизводительности является неприемлемым большую часть времени, и вы должны действительно сосредоточиться на читабельности вашего кода, если вы не находитесь в цикле.
В любом случае сначала напишите для удобочитаемости, а затем используйте профайлер производительности, чтобы определить ваши если вы действительно думаете, что у вас есть проблемы с производительностью.
Ответ 4
Для простого случая, когда это простая одиночная конкатенация, я чувствую, что это не стоит сложность string.Format
(и я не тестировал, но я подозреваю, что для простого случая, подобного этому, string.Format
может быть немного медленнее, что с синтаксическим разбором формата и всем). Как и Джон Скит, я предпочитаю явно не называть .ToString()
, поскольку это будет выполняться неявно с помощью перегрузки string.Concat(string, object)
, и я думаю, что код выглядит более чистым и легче читать без него.
Но для более чем нескольких конкатенаций (сколько субъективных) я определенно предпочитаю string.Format
. В какой-то момент я думаю, что как читаемость, так и производительность беззаботно связаны с конкатенацией.
Если в строку формата есть много параметров (опять-таки, "многие" субъективны), я обычно предпочитаю включать комментарии в замещающие аргументы, чтобы не потерять информацию о том, какое значение переходит к какому параметру. Надуманный пример:
Console.WriteLine(
"Dear {0} {1},\n\n" +
"Our records indicate that your {2}, \"{3}\", is due for {4} {5} shots.\n" +
"Please call our office at 1-900-382-5633 to make an appointment.\n\n" +
"Thank you,\n" +
"Eastern Vetinary",
/*0*/client.Title,
/*1*/client.LastName,
/*2*/client.Pet.Animal,
/*3*/client.Pet.Name,
/*4*/client.Pet.Gender == Gender.Male ? "his" : "her",
/*5*/client.Pet.Schedule[0]
);
Ответ 5
Если ваша строка была более сложной, при этом многие переменные были объединены, я бы выбрал строку .Format(). Но для размера строки и количества конкатенированных в вашем случае переменных я бы пошел с вашей первой версией, это больше spartan.
Ответ 6
Я взглянул на String.Format(используя Reflector), и он фактически создает StringBuilder, а затем вызывает AppendFormat. Так что это быстрее, чем concat для нескольких стимулов. Самый быстрый (я считаю) создание StringBuilder и выполнение вызовов для добавления вручную. Конечно, число "многих" зависит от угадывания.
Я бы использовал + (на самом деле и потому, что я программист VB в основном) для чего-то простого, как ваш пример. По мере усложнения я использую String.Format. Если есть много переменных, то я бы пошел на StringBuilder и Append, например, у нас есть код, который создает код, там я использую одну строку фактического кода для вывода одной строки сгенерированного кода.
Кажется, есть некоторые предположения о том, сколько строк создается для каждой из этих операций, поэтому давайте рассмотрим несколько простых примеров.
"C" + rowIndex.ToString();
"C" уже является строкой.
rowIndex.ToString() создает другую строку. (@manohard - никакого бокса rowIndex не произойдет)
Затем мы получим финальную строку.
Если взять пример
String.Format("C(0)",rowIndex);
то мы имеем "C {0}" как строку
rowIndex получает коробку для передачи в функцию
Создан новый строковый конструктор
AppendFormat вызывается в построителе строк - я не знаю подробностей о том, как AppendFormat функционирует, но позволяет предположить, что он ультраэффективен, ему все равно придется преобразовать boxed rowIndex в строку.
Затем преобразуйте stringbuilder в новую строку.
Я знаю, что StringBuilders пытается предотвратить появление бессмысленных копий памяти, но String.Format по-прежнему получает дополнительные накладные расходы по сравнению с простой конкатенацией.
Если мы возьмем пример с еще несколькими строками
"a" + rowIndex.ToString() + "b" + colIndex.ToString() + "c" + zIndex.ToString();
у нас есть 6 строк, которые будут одинаковыми для всех случаев.
Используя конкатенацию, мы также имеем 4 промежуточных строки плюс конечный результат. Именно эти промежуточные результаты устраняются с помощью String, Format (или StringBuilder).
Помните, что для создания каждой промежуточной строки предыдущий должен быть скопирован в новую ячейку памяти, а не просто распределение памяти, которое потенциально замедляется.
Ответ 7
Этот пример, вероятно, слишком тривиален, чтобы заметить разницу. На самом деле, я думаю, что в большинстве случаев компилятор может вообще оптимизировать любую разницу.
Однако, если бы я должен был предположить, что я дал бы string.Format()
край для более сложных сценариев. Но это больше похоже на ощущение, что он, скорее всего, будет лучше работать с использованием буфера вместо создания нескольких неизменяемых строк, а не на основе реальных данных.
Ответ 8
Мне нравится String.Format, потому что вы можете легко форматировать текст и читать, чем встроенная конкатенация, а также гораздо более гибкая, позволяющая отформатировать ваши параметры, однако для коротких целей, подобных вашим, я не вижу проблем с конкатенацией.
Для конкатенаций внутри циклов или больших строк вы всегда должны пытаться использовать класс StringBuilder.
Ответ 9
Я согласен с большим количеством пунктов выше, еще один момент, который, я считаю, следует упомянуть, - это ремонтопригодность кода. string.Format позволяет упростить изменение кода.
то есть. У меня есть сообщение
"The user is not authorized for location " + location
или
"The User is not authorized for location {0}"
если я когда-либо хотел изменить сообщение, чтобы сказать:
location + " does not allow this User Access"
или
"{0} does not allow this User Access"
с string.Format все, что мне нужно сделать, это изменить строку.
для конкатенации я должен изменить это сообщение
если используется в нескольких местах, можно сохранить выделение времени.
Ответ 10
string.Format, вероятно, лучший выбор, когда шаблон формата ( "C {0}" ) сохраняется в файле конфигурации (например, Web.config/App.config)
Ответ 11
Я немного профилировал различные строковые методы, включая string.Format, StringBuilder и конкатенацию строк. Конкатенация строк почти всегда превосходила другие методы построения строк. Итак, если производительность ключевая, то это лучше. Однако, если производительность не имеет решающего значения, я лично нахожу string.Format проще в коде. (Но это субъективная причина), однако StringBuilder, вероятно, наиболее эффективен в отношении использования памяти.
Ответ 12
Я предпочитаю String.Format относительно производительности
Ответ 13
У меня создалось впечатление, что string.format был быстрее, по-видимому, на 3 раза медленнее в этом тесте
string concat = "";
System.Diagnostics.Stopwatch sw1 = new System.Diagnostics.Stopwatch ();
sw1.Start();
for (int i = 0; i < 10000000; i++)
{
concat = string.Format("{0}{1}{2}{3}{4}{5}{6}{7}{8}{9}{10}","1", "2" , "3" , "4" , "5" , "6" , "7" , "8" , "9" , "10" , i);
}
sw1.Stop();
Response.Write("format: " + sw1.ElapsedMilliseconds.ToString());
System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch();
sw2.Start();
for (int i = 0; i < 10000000; i++)
{
concat = "1" + "2" + "3" + "4" + "5" + "6" + "7" + "8" + "9" + "10" + i;
}
sw2.Stop();
string.format занял 4.6 секунды, а при использовании '+' потребовалось 1.6 секунды.
Ответ 14
Конкатенация строк занимает больше памяти по сравнению с String.Format. Таким образом, лучший способ конкатенации строк - использовать объект String.Format или System.Text.StringBuilder.
Возьмем первый случай: "C" + rowIndex.ToString()
Пусть предположим, что rowIndex является типом значения, поэтому ToString() метод должен иметь значение Box для преобразования значения в String, а затем CLR создает память для новой строки с включенными значениями.
Где в качестве string.Format ожидает параметр объекта и принимает в rowIndex как объект и преобразует его в строку внутриотключенного, будет бокс, но он неотъемлемый, и также он не займет столько памяти, как в первом случае.
Для коротких строк это не имеет значения, насколько я предполагаю...