Когда лучше использовать String.Format или объединение строк?

120

У меня есть небольшой фрагмент кода, который анализирует значение индекса для определения ввода ячейки в Excel. Это заставило меня задуматься ...

какая разница между

xlsSheet.Write("C" + rowIndex.ToString(), null, title);

и

xlsSheet.Write(string.Format("C{0}", rowIndex), null, title);

Один «лучше» другого? И почему?

Гэвин Миллер
источник
4
Это похоже на stackoverflow.com/questions/16432/…
Джонатан С.
возможный дубликат Зачем использовать String.Format?
Druid

Ответы:

115

До C # 6

Честно говоря, я думаю, что первая версия проще - хотя я бы упростил ее до:

xlsSheet.Write("C" + rowIndex, null, title);

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

Строки формата отлично подходят для целей локализации и т. Д., Но в таком случае объединение проще и работает так же хорошо.

С C # 6

Строковая интерполяция упрощает чтение в C # 6. В этом случае ваш второй код выглядит следующим образом:

xlsSheet.Write($"C{rowIndex}", null, title);

что, вероятно, лучший вариант, ИМО.

Джон Скит
источник
6
@nawfal: См. msmvps.com/blogs/jon_skeet/archive/2008/10/08/…
Джон Скит
Я знаю я знаю. Это было сделано в шутку (кстати, я читал ссылку раньше, это было неплохо)
nawfal
Джон. Я всегда был поклонником мистера Рихтера и неукоснительно следовал советам по боксу и т.д. Однако, прочитав вашу (старую) статью, я теперь новообращенный. Спасибо
stevethethread
3
@ mbomb007: Теперь это на codeblog.jonskeet.uk/2008/10/08/…
Джон Скит,
4
Теперь, когда доступен C # 6, вы можете использовать новый синтаксис интерполяции строк, который, как мне кажется, еще более xlsSheet.Write($"C{rowIndex}", null, title);
удобен для
158

Мое первоначальное предпочтение (исходя из опыта работы с 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()имеет преимущество.
Дэн С.
источник
5
1) string.Format«безопасно» при использовании ReSharper; то есть он так же безопасен, как и любой другой код, который можно использовать [неправильно]. 2) string.Format это допускает «сейф» null: string.Format("A{0}B", (string)null)результаты в «AB». 3) Меня редко волнует такой уровень производительности (и поэтому я редко когда выхожу из игры StringBuilder) ...
Согласен по 2), пост редактирую. Невозможно проверить, было ли это безопасно в версии 1.1, но последняя версия действительно безопасна для нуля.
Дэн С.
Используется ли string.Concat, если один из операндов является вызовом метода с возвращаемым значением, а не параметром или переменной?
Ричард Коллетт
2
@RichardCollette Да, String.Concat используется, даже если вы объединяете возвращаемые значения вызовов методов, например, string s = "This " + MyMethod(arg) + " is a test";компилируется для String.Concat()вызова в режиме Release.
Дэн С.
Фантастический ответ; очень хорошо написано и объяснено.
Frank V
6

Я думаю, что первый вариант более читабелен, и это должно быть вашей главной заботой.

xlsSheet.Write("C" + rowIndex.ToString(), null, title);

string.Format использует StringBuilder под капотом (проверьте с помощью отражателя ), поэтому он не будет иметь никакого преимущества в производительности, если вы не выполните значительную конкатенацию. Это будет медленнее для вашего сценария, но на самом деле это решение по оптимизации микропроизводительности неуместно большую часть времени, и вам действительно следует сосредоточиться на удобочитаемости вашего кода, если вы не зацикливаетесь.

В любом случае, сначала напишите для удобства чтения, а затем используйте профилировщик производительности для определения ваших горячих точек, если вы действительно думаете, что у вас есть проблемы с производительностью.

Мартин Холлингсворт
источник
Отражатель: red-gate.com/products/reflector ANTS Профайлер: red-gate.com/products/ants_profiler/index.htm
Джейсон Слокомб
5

Для простого случая, когда это простая одиночная конкатенация, я чувствую, что это не стоит сложностей 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 Вы можете увидеть это в спецификации C # :

... в постоянных выражениях разрешены следующие конструкции:

...

  • Предопределенный бинарный оператор + ...

Вы также можете проверить это с помощью небольшого кода:

const string s =
    "This compiles successfully, " +
    "and you can see that it will " +
    "all be one string (named `s`) " +
    "at run time";
P Папа
источник
1
fyi, вы можете использовать @ "... многострочную строку" вместо всех конкатенаций.
Аарон Палмер
Да, но тогда вам нужно выровнять строку по левому краю. @ строки включают все новые строки и символы табуляции между кавычками.
P Daddy
Я знаю, что это старый, но это тот случай, когда я бы сказал, поместил строку формата в файл resx.
Энди
2
Вау, все сосредоточены на строковом литерале, а не на сути дела.
P Daddy
хехех - я только что заметил конкатенацию строк внутри вашегоString.Format()
Кристофер
3

Если бы ваша строка была более сложной со многими объединяемыми переменными, я бы выбрал string.Format (). Но для размера строки и количества конкатенируемых переменных в вашем случае я бы выбрал вашу первую версию, она более спартанская .

Аарон Палмер
источник
3

Я взглянул на 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).
Помните, что для создания каждой промежуточной строки предыдущая должна быть скопирована в новую ячейку памяти, это не просто выделение памяти, которое потенциально может быть медленным.

pipTheGeek
источник
4
Придираться. В "a" + ... + "b" + ... + "c" + ... у вас фактически не будет 4 промежуточных строк. Компилятор вызовет статический метод String.Concat (params string [] values), и все они будут объединены одновременно. Я все же предпочел бы string.Format для удобства чтения.
P Daddy
2

Мне нравится String.Format, потому что он может сделать ваш форматированный текст намного проще для просмотра и чтения, чем встроенная конкатенация, а также он гораздо более гибкий, позволяя вам форматировать ваши параметры, однако для короткого использования, такого как ваш, я не вижу проблем с конкатенацией.

Для конкатенации внутри циклов или в больших строках всегда следует пытаться использовать класс StringBuilder.

CMS
источник
2

Этот пример, вероятно, слишком тривиален, чтобы заметить разницу. Фактически, я думаю, что в большинстве случаев компилятор может вообще оптимизировать любую разницу.

Однако, если бы мне пришлось угадывать, я бы дал string.Format()преимущество для более сложных сценариев. Но это скорее инстинктивное ощущение, что, вероятно, лучше будет работать с использованием буфера вместо создания нескольких неизменяемых строк, а не на основе каких-либо реальных данных.

Джоэл Кохорн
источник
1

Я согласен со многими пунктами выше, еще один момент, который, как мне кажется, следует упомянуть, - это ремонтопригодность кода. 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"

со строкой. Отформатировать все, что мне нужно сделать, это изменить строку. для конкатенации я должен изменить это сообщение

при использовании в нескольких местах можно сэкономить время.

deankarn
источник
1

У меня создалось впечатление, что 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 секунды.

Kitemark76
источник
7
Компилятор распознает "1" + "2" + "3" + "4" + "5" + "6" + "7" + "8" + "9" + "10"один строковый литерал, поэтому строка становится "12345678910" + iбыстрее предыдущейstring.Format(...)
wertzui
0

string.Format, вероятно, является лучшим выбором, когда шаблон формата ("C {0}") хранится в файле конфигурации (например, Web.config / App.config)

Андрей Рыня
источник
0

Я провел небольшое профилирование различных строковых методов, включая string.Format, StringBuilder и конкатенацию строк. Конкатенация строк почти всегда превосходила другие методы построения строк. Итак, если производительность является ключевым фактором, то это лучше. Однако, если производительность не критична, я лично считаю, что string.Format легче использовать в коде. (Но это субъективная причина) Однако StringBuilder, вероятно, наиболее эффективен в отношении использования памяти.

dviljoen
источник
0

Я предпочитаю String.Format по производительности

Фарзад Дж.
источник
-1

Объединение строк требует больше памяти по сравнению с String.Format. Поэтому лучший способ объединить строки - использовать String.Format или System.Text.StringBuilder Object.

Возьмем первый случай: «C» + rowIndex.ToString () Предположим, что rowIndex является типом значения, поэтому метод ToString () должен преобразовать значение в String, а затем CLR создает память для новой строки с обоими включенными значениями.

Где, как string.Format ожидает параметр объекта и принимает rowIndex как объект и преобразует его в строку, внутренне извне, будет Boxing, но он является внутренним, а также не займет столько памяти, как в первом случае.

Думаю, для коротких струн это не имеет большого значения ...


источник