Когда лучше использовать 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 как объект и преобразует его в строку внутриотключенного, будет бокс, но он неотъемлемый, и также он не займет столько памяти, как в первом случае.

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