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

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

В чем разница между

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

а также

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

Один "лучше", чем другой? И почему?

15 ответов

Решение

До C# 6

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

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

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

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

С C# 6

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

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

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

Мое первоначальное предпочтение (исходящее из фона 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() имеет преимущество

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

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

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

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

Для простого случая, когда это простая одиночная конкатенация, я чувствую, что это не стоит сложности 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";

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

Я взглянул на String.Format (используя Reflector), и он фактически создает StringBuilder, а затем вызывает AppendFormat для него. Так что это быстрее, чем concat для нескольких перемешиваний. Самым быстрым (я полагаю) будет создание StringBuilder и выполнение вызовов Append вручную. Конечно, число "много" можно угадать. Я бы использовал + (на самом деле & потому что я в основном программист на VB) для чего-то столь же простого, как ваш пример. Поскольку это становится более сложным, я использую String.Format. Если есть много переменных, то я бы пошел на StringBuilder и Append, например, у нас есть код, который строит код, там я использую одну строку фактического кода для вывода одной строки сгенерированного кода.

Кажется, есть некоторые предположения о том, сколько строк создается для каждой из этих операций, поэтому давайте рассмотрим несколько простых примеров.

"C" + rowIndex.ToString();

"С" - это уже строка.
rowIndex.ToString () создает другую строку. (@manohard - бокс rowIndex не будет)
Затем мы получаем финальную строку.
Если мы возьмем пример

String.Format("C(0)",rowIndex);

тогда у нас есть "C{0}" в виде строки
rowIndex упаковывается для передачи в функцию
Новый string Builder создан
AppendFormat вызывается в строителе строк - я не знаю подробностей о том, как функции AppendFormat функционируют, но давайте предположим, что он очень эффективен, ему все равно придется преобразовать упакованный rowIndex в строку.
Затем преобразуйте строку в новую строку.
Я знаю, что StringBuilders пытаются предотвратить бессмысленное копирование памяти, но String.Format по-прежнему приводит к дополнительным издержкам по сравнению с простой конкатенацией.

Если мы сейчас возьмем пример с несколькими строками

"a" + rowIndex.ToString() + "b" + colIndex.ToString() + "c" + zIndex.ToString();

у нас есть 6 строк для начала, которые будут одинаковыми для всех случаев.
Используя конкатенацию, мы также имеем 4 промежуточные строки плюс конечный результат. Это те промежуточные результаты, которые устраняются с помощью String,Format (или StringBuilder).
Помните, что для создания каждой промежуточной строки предыдущая должна быть скопирована в новую область памяти, а не только выделение памяти, которая может быть медленной.

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

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

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

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

О производительности:

      void Main()
{
    var start = CurrentTimeMillis();
    for (var i = 0; i < 1000000; i++)
    {
        var s = "Hi " + i.ToString() + "; Hi to you " + (i * 2).ToString();
    }
    var end = CurrentTimeMillis();
    Console.WriteLine("Concatenation = " + ((end - start)).ToString() + " millisecond");
    start = CurrentTimeMillis();
    for (var i = 0; i < 1000000; i++)
    {
        var s = String.Format("Hi {0}; Hi to you {1}", i, +i * 2);
    }
    end = CurrentTimeMillis();
    Console.WriteLine("Format = " + ((end - start)).ToString() + " millisecond");
    start = CurrentTimeMillis();
    for (var i = 0; i < 1000000; i++)
    {
        var s = String.Concat("Hi ", i.ToString(), "; Hi to you ", (i * 2).ToString());
    }
    end = CurrentTimeMillis();
    Console.WriteLine("Strng.concat = " + ((end - start)).ToString() + " millisecond");

    start = CurrentTimeMillis();
    for (int i = 0; i < 1000000; i++)
    {
        StringBuilder bldString = new StringBuilder("Hi ");
        bldString.Append(i).Append("; Hi to you ").Append(i * 2);
    }
    end = CurrentTimeMillis();
    Console.WriteLine("String Builder = " + ((end - start)) + " millisecond");
}

private static readonly DateTime Jan1st1970 = new DateTime
    (1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

public static long CurrentTimeMillis()
{
    return (long)(DateTime.UtcNow - Jan1st1970).TotalMilliseconds;
}

Результат

      Concatenation = 69 millisecond
Format = 142 millisecond
Strng.concat = 62 millisecond
String Builder = 91 millisecond

Чтобы сделать это справедливым сравнение, я создаю новый StringBuilder, а не тот, который был создан вне цикла (который, вероятно, будет быстрее из-за добавления цикла в конце пространства перераспределения одного строителя).

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

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

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

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

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

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

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

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

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