У меня есть небольшой фрагмент кода, который анализирует значение индекса для определения ввода ячейки в Excel. Это заставило меня задуматься ...
какая разница между
xlsSheet.Write("C" + rowIndex.ToString(), null, title);
и
xlsSheet.Write(string.Format("C{0}", rowIndex), null, title);
Один «лучше» другого? И почему?
возможный дубликат Зачем использовать String.Format?





До C# 6
Честно говоря, я думаю, что первая версия проще - хотя я бы упростил ее до:
xlsSheet.Write("C" + rowIndex, null, title);
Я подозреваю, что другие ответы май говорят о снижении производительности, но, честно говоря, это будет минимальный если присутствует вообще - и эта версия конкатенации не требует синтаксического анализа строки формата.
Строки формата отлично подходят для целей локализации и т. д., Но в таком случае объединение проще и работает так же хорошо.
С C# 6
Строковая интерполяция упрощает чтение в C# 6. В этом случае ваш второй код выглядит следующим образом:
xlsSheet.Write($"C{rowIndex}", null, title);
что, вероятно, лучший вариант, ИМО.
@nawfal: см. msmvps.com/blogs/jon_skeet/archive/2008/10/08/…
Знаю, знаю. Это было сделано в шутку (кстати, раньше читал ссылку, хорошо читал)
Джон. Я всегда был поклонником мистера Рихтера и неукоснительно следовал советам по боксу и т. д. Однако, прочитав вашу (старую) статью, я теперь новообращенный. Спасибо
@ mbomb007: Сейчас на codeblog.jonskeet.uk/2008/10/08/…
Теперь, когда доступен C# 6, вы можете использовать новый синтаксис интерполяции строк, который, как мне кажется, еще более удобен для чтения: xlsSheet.Write($"C{rowIndex}", null, title);
@JonSkeet: эта ссылка тоже мертв
@TimSchmelter: Хм ... не для меня ... проверил только что. Какая у вас ошибка?
@JonSkeet: в chrome: ERR_NAME_RESOLUTION_FAILED, в IE тоже. Но http://codeblog.jonskeet.uk/ тоже недоступен
@TimSchmelter: Хорошо, тогда это похоже на проблему ... понятия не имею, почему это могло быть, но я просто ухожу с работы, поэтому попробую на мобильном телефоне и дома ...
@JonSkeet - Ваше изображение в профиле звучит знакомо ... разве не "Сдавайся и иди ТАК, и позволь Джону Скиту спасти принцессу". Кстати, это был лучший способ спасти принцессу .. Я один из ваших самых больших поклонников, вы несколько раз спасали принцессу для меня ..: D
Итак, теперь с C# 6 вы больше никогда не будете использовать конкатенацию строк? всегда строковая интерполяция? нет вариантов использования для конкатенации строк?
@BornToCode: я бы не сказал никогда, но я бы как правило использовал интерполяцию строк.
Я думаю, что @P Daddy создал для этого хорошую эвристику в своем ответе: «Если я просто не объединяю значение непосредственно в начало или конец строкового литерала, я почти всегда использую интерполяцию строк», поэтому, следуя этому правилу, я обнаружил, что конкретный пример, который вы процитировали, "C" + rowIndex на самом деле более читабелен, чем $ "C {rowIndex}". Но это могло быть только мое мнение .. Трудно противоречить великому Скиту;)
Если бы ваша строка была более сложной со многими объединяемыми переменными, я бы выбрал string.Format (). Но для размера строки и количества конкатенированных переменных в вашем случае я бы выбрал вашу первую версию, она больше спартанский.
Этот пример, вероятно, слишком тривиален, чтобы заметить разницу. На самом деле, я думаю, что в большинстве случаев компилятор может вообще оптимизировать любую разницу.
Однако, если бы мне пришлось угадывать, я бы дал string.Format() преимущество для более сложных сценариев. Но это скорее инстинктивное ощущение, что, вероятно, лучше будет работать с использованием буфера вместо создания нескольких неизменяемых строк, а не на основе каких-либо реальных данных.
Мне нравится String.Format, потому что он может сделать ваш форматированный текст намного проще для просмотра и чтения, чем встроенная конкатенация, а также он гораздо более гибкий, позволяя форматировать ваши параметры, однако для короткого использования, такого как ваше, я не вижу проблем с конкатенацией.
Для конкатенации внутри циклов или в больших строках вы всегда должны пытаться использовать класс StringBuilder.
Объединение строк требует больше памяти по сравнению с String.Format. Поэтому лучший способ объединить строки - использовать String.Format или System.Text.StringBuilder Object.
Возьмем первый случай: "C" + rowIndex.ToString () Предположим, что rowIndex является типом значения, поэтому метод ToString () должен преобразовать значение в String, а затем CLR создает память для новой строки с обоими включенными значениями.
Где, как string.Format ожидает параметр объекта и принимает rowIndex как объект и преобразует его в строку внутренне вне курса, будет бокс, но он является внутренним, а также не займет столько памяти, как в первом случае.
Думаю, для коротких струн это не имеет особого значения ...
Я думаю, что первый вариант более читабелен, и это должно быть вашей главной заботой.
xlsSheet.Write("C" + rowIndex.ToString(), null, title);
string.Format использует StringBuilder под капотом (проверьте с помощью отражатель), поэтому он не будет иметь никакого преимущества в производительности, если вы не выполните значительную конкатенацию. Это будет медленнее для вашего сценария, но на самом деле это решение по оптимизации микропроизводительности неуместно большую часть времени, и вам действительно следует сосредоточиться на удобочитаемости вашего кода, если вы не зацикливаетесь.
В любом случае, сначала напишите для удобства чтения, а затем используйте профилировщик производительности для определения ваших горячих точек, если вы действительно думаете, что у вас есть проблемы с производительностью.
Отражатель: red-gate.com/products/reflector ANTS Профайлер: red-gate.com/products/ants_profiler/index.htm
Для простого случая, когда это простая одиночная конкатенация, я считаю, что это не стоит сложности 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 Veterinary",
/*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]
);
Мне приходит в голову, что приведенный мной пример немного сбивает с толку, потому что, похоже, я использовал здесь конкатенацию и то и другое и string.Format. И да, логически и лексически это то, что я сделал. Но все конкатенации будут оптимизированы компилятором 1, поскольку все они являются строковыми литералами. Таким образом, во время выполнения будет одна строка. Итак, я думаю, я должен сказать, что предпочитаю избегать многих конкатенаций во время выполнения.
Конечно, большая часть этой темы уже устарела, если вы все еще не застряли на C# 5 или более ранней версии. Теперь у нас есть интерполированные строки, который по удобочитаемости намного превосходит string.Format почти во всех случаях. В наши дни, если я просто не объединяю значение непосредственно в начало или конец строкового литерала, я почти всегда использую интерполяцию строк. Сегодня я бы написал свой предыдущий пример так:
Console.WriteLine(
$"Dear {client.Title} {client.LastName},\n\n" +
$"Our records indicate that your {client.Pet.Animal}, \"{client.Pet.Name}\", " +
$"is due for {(client.Pet.Gender == Gender.Male ? "his" : "her")} " +
$"{client.Pet.Schedule[0]} shots.\n" +
"Please call our office at 1-900-382-5633 to make an appointment.\n\n" +
"Thank you,\n" +
"Eastern Veterinary"
);
Таким образом, вы теряете конкатенацию во время компиляции. Каждая интерполированная строка превращается компилятором в вызов string.Format, и их результаты объединяются во время выполнения. Это означает, что производительность во время выполнения приносится в жертву удобочитаемости. В большинстве случаев это стоящая жертва, потому что потери времени выполнения незначительны. Однако в критически важном для производительности коде вам может потребоваться профилирование различных решений.
1 You can see this in the C# specification:
... the following constructs are permitted in constant expressions:
...
- The predefined + ... binary operator...
You can also verify it with a little code:
const string s =
"This compiles successfully, " +
"and you can see that it will " +
"all be one string (named `s`) " +
"at run time";
fyi, вы можете использовать @ "... многострочную строку" вместо всех конкатенаций.
Да, но тогда вам нужно выровнять строку по левому краю. @ строки включают все новые строки и символы табуляции между кавычками.
Я знаю, что это старый, но это тот случай, когда я бы сказал, поместите строку формата в файл resx.
Вау, все сосредоточились на строковом литерале, а не на сути дела.
хехех - я только что заметил конкатенацию строк внутри вашего String.Format()
Я взглянул на String.Format (используя Reflector), и он фактически создает StringBuilder, а затем вызывает для него AppendFormat. Так что это быстрее, чем concat для нескольких перемешиваний. Самым быстрым (я считаю) было бы создание StringBuilder и выполнение вызовов Append вручную. Конечно, количество «многих» остается только гадать. Я бы использовал + (на самом деле и потому что я в основном программист VB) для чего-то столь же простого, как ваш пример. По мере усложнения я использую String.Format. Если есть МНОГО переменных, я бы выбрал StringBuilder и Append, например, у нас есть код, который строит код, там я использую одну строку фактического кода для вывода одной строки сгенерированного кода.
Кажется, есть некоторые предположения о том, сколько строк создается для каждой из этих операций, поэтому давайте рассмотрим несколько простых примеров.
"C" + rowIndex.ToString();
"C" уже является строкой.
rowIndex.ToString () создает другую строку. (@manohard - упаковки rowIndex не происходит)
Дальше получаем финальную строку.
Если взять пример
String.Format("C(0)",rowIndex);
тогда у нас есть "C {0}" как строка
rowIndex помещается в коробку для передачи в функцию
Создан новый конструктор строк
AppendFormat вызывается в построителе строк - я не знаю подробностей того, как работает AppendFormat, но предположим, что он очень эффективен, ему все равно придется преобразовать упакованный rowIndex в строку.
Затем преобразуйте конструктор строк в новую строку.
Я знаю, что StringBuilders пытается предотвратить создание бессмысленных копий памяти, но String.Format по-прежнему вызывает дополнительные накладные расходы по сравнению с простой конкатенацией.
Если мы теперь возьмем пример с еще несколькими строками
"a" + rowIndex.ToString() + "b" + colIndex.ToString() + "c" + zIndex.ToString();
у нас есть 6 строк для начала, которые будут одинаковыми для всех случаев.
Используя конкатенацию, мы также получаем 4 промежуточные строки плюс окончательный результат. Это те промежуточные результаты, которые исключаются с помощью String, Format (или StringBuilder) .
Помните, что для создания каждой промежуточной строки предыдущая должна быть скопирована в новое место в памяти, это не просто выделение памяти, которое потенциально может быть медленным.
Нитпик. В "a" + ... + "b" + ... + "c" + ... у вас фактически не будет 4 промежуточных строк. Компилятор вызовет статический метод String.Concat (params string [] values), и все они будут объединены одновременно. Я все же предпочел бы string.Format для удобства чтения.
string.Format, вероятно, является лучшим выбором, когда шаблон формата ("C {0}") хранится в файле конфигурации (например, Web.config / App.config)
Я провел небольшое профилирование различных строковых методов, включая string.Format, StringBuilder и конкатенацию строк. Конкатенация строк почти всегда превосходила другие методы построения строк. Итак, если производительность является ключевым фактором, то это лучше. Однако, если производительность не критична, я лично считаю, что string.Format легче использовать в коде. (Но это субъективная причина) Однако StringBuilder, вероятно, наиболее эффективен в отношении использования памяти.
Мое первоначальное предпочтение (исходя из опыта работы с C++) было для String.Format. Позже я отказался от этого по следующим причинам:
- Конкатенация строк допускает нулевые значения, String.Format - нет. Запись «s1 + null + s2» не прерывается, она просто обрабатывает нулевое значение как String.Empty. Что ж, это может зависеть от вашего конкретного сценария - бывают случаи, когда вам нужна ошибка вместо молчаливого игнорирования пустого имени 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() имеет преимущество.1) string.Format «безопасен» при использовании ReSharper; то есть он так же безопасен, как и любой другой код, который можно использовать [неправильно]. 2) string.Formatделает разрешает "безопасный" null: string.Format("A{0}B", (string)null) дает "AB". 3) Меня редко волнует этот уровень производительности (и с этой целью это редкий день, когда я вытаскиваю StringBuilder) ...
Согласен по 2), пост редактирую. Невозможно проверить, было ли это безопасным в версии 1.1, но последняя версия действительно безопасна для нуля.
Используется ли string.Concat, если один из операндов является вызовом метода с возвращаемым значением, а не параметром или переменной?
@RichardCollette Да, String.Concat используется, даже если вы объединяете возвращаемые значения вызовов методов, например. string s = "This " + MyMethod(arg) + " is a test"; компилируется в вызов String.Concat() в режиме Release.
Фантастический ответ; очень хорошо написано и объяснено.
~ 3000 очков, ~ 50% ваших очков, полученных за этот ответ, и ваш ответ превосходит ответ Джона. Извините, вы все еще живы или Джон убил вас?
@MehdiDehghani, все еще жив, рад, что вы нашли это полезным.
@DanC. рад слышать, береги себя.
Я предпочитаю String.Format по производительности
Я согласен со многими пунктами выше, еще один момент, который, я считаю, следует упомянуть, - это ремонтопригодность кода. 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 был быстрее, кажется, в этом тесте в 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 секунды.
Компилятор распознает "1" + "2" + "3" + "4" + "5" + "6" + "7" + "8" + "9" + "10" как один строковый литерал, поэтому строка фактически становится "12345678910" + i, что быстрее предыдущего string.Format(...).
Это похоже на stackoverflow.com/questions/16432/…