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

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

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

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

и

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

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

Это похоже на stackoverflow.com/questions/16432/…

Jonathan S. 18.11.2008 02:06

возможный дубликат Зачем использовать String.Format?

Druid 02.08.2012 11:13
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
123
2
91 333
14
Перейти к ответу Данный вопрос помечен как решенный

Ответы 14

Ответ принят как подходящий

До 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/…

Jon Skeet 12.05.2013 15:09

Знаю, знаю. Это было сделано в шутку (кстати, раньше читал ссылку, хорошо читал)

nawfal 12.05.2013 15:24

Джон. Я всегда был поклонником мистера Рихтера и неукоснительно следовал советам по боксу и т. д. Однако, прочитав вашу (старую) статью, я теперь новообращенный. Спасибо

stevethethread 21.01.2014 18:42

@ mbomb007: Сейчас на codeblog.jonskeet.uk/2008/10/08/…

Jon Skeet 14.05.2015 22:57

Теперь, когда доступен C# 6, вы можете использовать новый синтаксис интерполяции строк, который, как мне кажется, еще более удобен для чтения: xlsSheet.Write($"C{rowIndex}", null, title);

HotN 24.07.2015 17:54

@JonSkeet: эта ссылка тоже мертв

Tim Schmelter 05.09.2016 16:17

@TimSchmelter: Хм ... не для меня ... проверил только что. Какая у вас ошибка?

Jon Skeet 05.09.2016 16:53

@JonSkeet: в chrome: ERR_NAME_RESOLUTION_FAILED, в IE тоже. Но http://codeblog.jonskeet.uk/ тоже недоступен

Tim Schmelter 05.09.2016 16:55

@TimSchmelter: Хорошо, тогда это похоже на проблему ... понятия не имею, почему это могло быть, но я просто ухожу с работы, поэтому попробую на мобильном телефоне и дома ...

Jon Skeet 05.09.2016 17:07

@JonSkeet - Ваше изображение в профиле звучит знакомо ... разве не "Сдавайся и иди ТАК, и позволь Джону Скиту спасти принцессу". Кстати, это был лучший способ спасти принцессу .. Я один из ваших самых больших поклонников, вы несколько раз спасали принцессу для меня ..: D

user240141 18.08.2017 11:04

Итак, теперь с C# 6 вы больше никогда не будете использовать конкатенацию строк? всегда строковая интерполяция? нет вариантов использования для конкатенации строк?

BornToCode 31.05.2018 10:01

@BornToCode: я бы не сказал никогда, но я бы как правило использовал интерполяцию строк.

Jon Skeet 31.05.2018 12:45

Я думаю, что @P Daddy создал для этого хорошую эвристику в своем ответе: «Если я просто не объединяю значение непосредственно в начало или конец строкового литерала, я почти всегда использую интерполяцию строк», поэтому, следуя этому правилу, я обнаружил, что конкретный пример, который вы процитировали, "C" + rowIndex на самом деле более читабелен, чем $ "C {rowIndex}". Но это могло быть только мое мнение .. Трудно противоречить великому Скиту;)

BornToCode 31.05.2018 19:57

Если бы ваша строка была более сложной со многими объединяемыми переменными, я бы выбрал 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

Jason Slocomb 18.11.2008 02:01

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

Aaron Palmer 18.11.2008 16:48

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

P Daddy 18.11.2008 17:10

Я знаю, что это старый, но это тот случай, когда я бы сказал, поместите строку формата в файл resx.

Andy 21.03.2012 06:30

Вау, все сосредоточились на строковом литерале, а не на сути дела.

P Daddy 21.03.2012 06:52

хехех - я только что заметил конкатенацию строк внутри вашего String.Format()

Kristopher 09.01.2018 17:29

Я взглянул на 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 для удобства чтения.

P Daddy 19.11.2008 04:08

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) ...

user166390 20.03.2012 02:46

Согласен по 2), пост редактирую. Невозможно проверить, было ли это безопасным в версии 1.1, но последняя версия действительно безопасна для нуля.

Dan C. 20.03.2012 13:27

Используется ли string.Concat, если один из операндов является вызовом метода с возвращаемым значением, а не параметром или переменной?

Richard Collette 14.01.2013 19:26

@RichardCollette Да, String.Concat используется, даже если вы объединяете возвращаемые значения вызовов методов, например. string s = "This " + MyMethod(arg) + " is a test"; компилируется в вызов String.Concat() в режиме Release.

Dan C. 22.01.2013 22:29

Фантастический ответ; очень хорошо написано и объяснено.

Frank V 21.03.2014 22:23

~ 3000 очков, ~ 50% ваших очков, полученных за этот ответ, и ваш ответ превосходит ответ Джона. Извините, вы все еще живы или Джон убил вас?

Mehdi Dehghani 31.01.2019 15:53

@MehdiDehghani, все еще жив, рад, что вы нашли это полезным.

Dan C. 18.03.2019 15:31

@DanC. рад слышать, береги себя.

Mehdi Dehghani 18.03.2019 15:34

Я предпочитаю 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(...).

wertzui 23.09.2015 10:44

Другие вопросы по теме