Вывод строки: формат или конкат в C#?

Допустим, вы хотите выводить или объединять строки. Какой из следующих стилей вы предпочитаете?

  • var p = new { FirstName = "Bill", LastName = "Gates" };

  • Console.WriteLine("{0} {1}", p.FirstName, p.LastName);

  • Console.WriteLine(p.FirstName + " " + p.LastName);

Вы предпочитаете использовать формат или просто объединяете строки? Какой твой любимый? У кого-то из них болят глаза?

Есть ли у вас какие-нибудь рациональные аргументы в пользу того, чтобы использовать одно, а не другое?

Я бы выбрал второй.

Ответов (25)

Решение

Попробуйте этот код.

Это немного измененная версия вашего кода.
1. Я удалил Console.WriteLine, поскольку он, вероятно, на несколько порядков медленнее, чем то, что я пытаюсь измерить.
2. Я запускаю секундомер перед циклом и останавливаю его сразу после этого, таким образом я не теряю точности, если для выполнения функции требуется, например, 26,4 тика.
3. То, как вы разделили результат на несколько итераций, было неправильным. Посмотрите, что произойдет, если у вас есть 1000 миллисекунд и 100 миллисекунд. В обоих случаях после деления на 1000000 вы получите 0 мс.

Stopwatch s = new Stopwatch();

var p = new { FirstName = "Bill", LastName = "Gates" };

int n = 1000000;
long fElapsedMilliseconds = 0, fElapsedTicks = 0, cElapsedMilliseconds = 0, cElapsedTicks = 0;

string result;
s.Start();
for (var i = 0; i < n; i++)
    result = (p.FirstName + " " + p.LastName);
s.Stop();
cElapsedMilliseconds = s.ElapsedMilliseconds;
cElapsedTicks = s.ElapsedTicks;
s.Reset();
s.Start();
for (var i = 0; i < n; i++)
    result = string.Format("{0} {1}", p.FirstName, p.LastName);
s.Stop();
fElapsedMilliseconds = s.ElapsedMilliseconds;
fElapsedTicks = s.ElapsedTicks;
s.Reset();


Console.Clear();
Console.WriteLine(n.ToString()+" x result = string.Format(\"{0} {1}\", p.FirstName, p.LastName); took: " + (fElapsedMilliseconds) + "ms - " + (fElapsedTicks) + " ticks");
Console.WriteLine(n.ToString() + " x result = (p.FirstName + \" \" + p.LastName); took: " + (cElapsedMilliseconds) + "ms - " + (cElapsedTicks) + " ticks");
Thread.Sleep(4000);

Вот мои результаты:

1000000 x результат = строка.Формат ("{0} {1}", p.FirstName, p.LastName); ушло: 618мс - 2213706 тиков
1000000 x результат = (p.FirstName + "" + p.LastName); ушло: 166мс - 595610 тиков

Для очень простых манипуляций я бы использовал конкатенацию, но как только вы выйдете за пределы 2 или 3 элементов, формат станет более подходящим IMO.

Еще одна причина предпочесть String.Format заключается в том, что строки .NET неизменяемы и таким образом создается меньше временных / промежуточных копий.

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

Объединение строк прекрасно в таком простом сценарии - это сложнее с чем-то более сложным, чем это, даже с LastName, FirstName. С помощью формата вы можете сразу увидеть, какой будет окончательная структура строки при чтении кода, при конкатенации становится почти невозможным сразу различить конечный результат (за исключением очень простого примера, подобного этому).

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

Если вы используете .NET 3.5, вы можете использовать метод расширения, подобный этому, и получить простой поток без синтаксиса манжеты, например:

string str = "{0} {1} is my friend. {3}, {2} is my boss.".FormatWith(prop1,prop2,prop3,prop4);

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

string name = String.Format(ApplicationStrings.General.InformalUserNameFormat,this.FirstName,this.LastName);

Строки неизменяемы, это означает, что в вашем коде снова и снова используется один и тот же крошечный кусок памяти. Добавление одних и тех же двух строк вместе и создание одной и той же новой строки снова и снова не влияет на память. .Net достаточно умен, чтобы использовать ту же ссылку на память. Поэтому ваш код на самом деле не проверяет разницу между двумя методами concat.

Примерьте это на размер:

Stopwatch s = new Stopwatch();

int n = 1000000;
long fElapsedMilliseconds = 0, fElapsedTicks = 0, cElapsedMilliseconds = 0, cElapsedTicks = 0, sbElapsedMilliseconds = 0, sbElapsedTicks = 0;

Random random = new Random(DateTime.Now.Millisecond);

string result;
s.Start();
for (var i = 0; i < n; i++)
    result = (random.Next().ToString() + " " + random.Next().ToString());
s.Stop();
cElapsedMilliseconds = s.ElapsedMilliseconds;
cElapsedTicks = s.ElapsedTicks;
s.Reset();

s.Start();
for (var i = 0; i < n; i++)
    result = string.Format("{0} {1}", random.Next().ToString(), random.Next().ToString());
s.Stop();
fElapsedMilliseconds = s.ElapsedMilliseconds;
fElapsedTicks = s.ElapsedTicks;
s.Reset();

StringBuilder sb = new StringBuilder();
s.Start();
for(var i = 0; i < n; i++){
    sb.Clear();
    sb.Append(random.Next().ToString());
    sb.Append(" ");
    sb.Append(random.Next().ToString());
    result = sb.ToString();
}
s.Stop();
sbElapsedMilliseconds = s.ElapsedMilliseconds;
sbElapsedTicks = s.ElapsedTicks;
s.Reset();

Console.WriteLine(n.ToString() + " x result = string.Format(\"{0} {1}\", p.FirstName, p.LastName); took: " + (fElapsedMilliseconds) + "ms - " + (fElapsedTicks) + " ticks");
Console.WriteLine(n.ToString() + " x result = (p.FirstName + \" \" + p.LastName); took: " + (cElapsedMilliseconds) + "ms - " + (cElapsedTicks) + " ticks");
Console.WriteLine(n.ToString() + " x sb.Clear();sb.Append(random.Next().ToString()); sb.Append(\" \"); sb.Append(random.Next().ToString()); result = sb.ToString(); took: " + (sbElapsedMilliseconds) + "ms - " + (sbElapsedTicks) + " ticks");
Console.WriteLine("****************");
Console.WriteLine("Press Enter to Quit");
Console.ReadLine();

Пример вывода:

1000000 x result = string.Format("{0} {1}", p.FirstName, p.LastName); took: 513ms - 1499816 ticks
1000000 x result = (p.FirstName + " " + p.LastName); took: 393ms - 1150148 ticks
1000000 x sb.Clear();sb.Append(random.Next().ToString()); sb.Append(" "); sb.Append(random.Next().ToString()); result = sb.ToString(); took: 405ms - 1185816 ticks

Начиная с C# 6.0 для этого можно использовать интерполированные строки , что еще больше упрощает формат.

var name = "Bill";
var surname = "Gates";
MessageBox.Show($"Welcome to the show, {name} {surname}!");

Интерполированное строковое выражение выглядит как строка шаблона, содержащая выражения. Интерполированное строковое выражение создает строку, заменяя содержащиеся выражения на представления ToString результатов выражений.

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

Также обратитесь к этой статье dotnetperls по интерполяции строк.

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

Через неделю, 19 августа 2015 г., этому вопросу исполнится ровно 7 (семь) лет. Теперь есть лучший способ сделать это. Лучше с точки зрения ремонтопригодности, поскольку я не проводил никаких тестов производительности по сравнению с простым объединением строк (но имеет ли это значение в наши дни - разница в несколько миллисекунд?). Новый способ сделать это в C# 6.0 :

var p = new { FirstName = "Bill", LastName = "Gates" };
var fullname = $"{p.FirstName} {p.LastName}";

Эта новая функция лучше , IMO, и на самом деле лучше в нашем случае, поскольку у нас есть коды, в которых мы создаем строки запроса, значения которых зависят от некоторых факторов. Представьте себе одну строку запроса, в которой у нас есть 6 аргументов. Поэтому вместо того, чтобы делать, например:

var qs = string.Format("q1={0}&q2={1}&q3={2}&q4={3}&q5={4}&q6={5}", 
    someVar, anotherVarWithLongName, var3, var4, var5, var6)

in может быть записано так, и его легче читать:

var qs=$"q1={someVar}&q2={anotherVarWithLongName}&q3={var3}&q4={var4}&q5={var5}&q6={var6}";

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

Выберите подходящий инструмент в зависимости от задачи: D Какой бы ни выглядел лучше!

  1. Форматирование - это «.NET» способ сделать это. Некоторые инструменты рефакторинга (например, Refactor!) Даже предлагают рефакторинг кода в стиле concat для использования стиля форматирования.
  2. Форматирование легче оптимизировать для компилятора (хотя второй, вероятно, будет переработан для использования быстрого метода Concat).
  3. Форматирование обычно читается легче (особенно с «модным» форматированием).
  4. Форматирование означает неявные вызовы '.ToString' для всех переменных, что удобно для чтения.
  5. Согласно «Эффективному C#», реализации .NET 'WriteLine' и 'Format' перепутались, они автоматически запаковывают все типы значений (что плохо). «Эффективный C#» советует явно выполнять вызовы '.ToString', что ИМХО является подделкой (см. Сообщение Джеффа )
  6. На данный момент подсказки типа форматирования не проверяются компилятором, что приводит к ошибкам во время выполнения. Однако это может быть изменено в будущих версиях.

Для базовой конкатенации строк я обычно использую второй стиль - более легкий для чтения и более простой. Однако, если я делаю более сложную комбинацию строк, я обычно выбираю String.Format.

String.Format экономит на множестве кавычек и плюсов ...

Console.WriteLine("User {0} accessed {1} on {2}.", user.Name, fileName, timestamp);
vs
Console.WriteLine("User " + user.Name + " accessed " + fileName + " on " + timestamp + ".");

Сохранено всего несколько символов, но я думаю, что в этом примере формат делает его намного чище.

Обычно я предпочитаю первое, так как особенно когда строки становятся длинными, их становится намного легче читать.

Другое преимущество, я считаю, заключается в производительности, поскольку последний фактически выполняет 2 оператора создания строки перед передачей последней строки в метод Console.Write. Я считаю, что String.Format использует StringBuilder под обложками, поэтому множественные конкатенации избегаются.

Однако следует отметить, что если параметры, которые вы передаете в String.Format (и другие подобные методы, такие как Console.Write), являются типами значений, то они будут упакованы перед передачей, что может привести к снижению производительности. Публикация в блоге об этом здесь .

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

Если это будет показано пользователю, я бы использовал String.Format, чтобы я мог локализовать, если мне нужно - и FxCop проверит его орфографию для меня, на всякий случай :)

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

Если это для создания запроса, такого как SQL, я бы использовал Linq .

Если для объединения строк внутри цикла, я бы использовал StringBuilder, чтобы избежать проблем с производительностью.

Если это для какого-то вывода, который пользователь не увидит и не повлияет на производительность, я бы использовал String.Format, потому что у меня есть привычка использовать его в любом случае, и я просто к этому привык :)

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

Используя следующий код:

    System.Diagnostics.Stopwatch s = new System.Diagnostics.Stopwatch();

    var p = new { FirstName = "Bill", LastName = "Gates" };

    s.Start();
    Console.WriteLine("{0} {1}", p.FirstName, p.LastName);
    s.Stop();
    Console.WriteLine("Console.WriteLine(\"{0} {1}\", p.FirstName, p.LastName); took: " + s.ElapsedMilliseconds + "ms - " + s.ElapsedTicks + " ticks");

    s.Reset();
    s.Start();
    Console.WriteLine(p.FirstName + " " + p.LastName);
    s.Stop();

    Console.WriteLine("Console.WriteLine(p.FirstName + \" \" + p.LastName); took: " + s.ElapsedMilliseconds + "ms - " + s.ElapsedTicks + " ticks");

Получил следующие результаты:

Bill Gates
Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took: 2ms - 7280 ticks
Bill Gates
Console.WriteLine(p.FirstName + " " + p.LastName); took: 0ms - 67 ticks

Использование метода форматирования более чем в 100 раз медленнее !! Конкатенация даже не регистрировалась как 1 мс, поэтому я также выводю тики таймера.

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

Bill Gates
Console.WriteLine(p.FirstName + " " + p.LastName); took: 8ms - 30488 ticks
Bill Gates
Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took: 0ms - 182 ticks

Таким образом, порядок операций имеет ОГРОМНУЮ разницу, или, скорее, самая первая операция ВСЕГДА намного медленнее.

Вот результаты прогона, когда операции выполняются более одного раза. Я пытался изменить порядок, но, как правило, все происходит по тем же правилам, если первый результат игнорируется:

Bill Gates
Console.WriteLine(FirstName + " " + LastName); took: 5ms - 20335 ticks
Bill Gates
Console.WriteLine(FirstName + " " + LastName); took: 0ms - 156 ticks
Bill Gates
Console.WriteLine(FirstName + " " + LastName); took: 0ms - 122 ticks
Bill Gates
Console.WriteLine("{0} {1}", FirstName, LastName); took: 0ms - 181 ticks
Bill Gates
Console.WriteLine("{0} {1}", FirstName, LastName); took: 0ms - 122 ticks
Bill Gates
String.Concat(FirstName, " ", LastName); took: 0ms - 142 ticks
Bill Gates
String.Concat(FirstName, " ", LastName); took: 0ms - 117 ticks

Как вы можете видеть, последующие запуски одного и того же метода (я реорганизовал код в 3 метода) постепенно выполняются быстрее. Самым быстрым, по-видимому, является метод Console.WriteLine (String.Concat (...)), за которым следует обычная конкатенация, а затем операции форматирования.

Первоначальная задержка при запуске, скорее всего, является инициализацией Console Stream, поскольку размещение Console.Writeline («Start!») Перед первой операцией возвращает все времена в соответствие.

На самом деле, я проводил эти тесты вчера, но было уже поздно, поэтому я не отправлял свои ответы.

Суть в том, что в среднем они занимают и то, и другое одинаковое время. Я провел тест более 100000 итераций.

Я тоже попробую с StringBuilder, и я отправлю код и результаты, когда вернусь домой.

Вот мои результаты за 100 000 итераций:

Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took (avg): 0ms - 689 ticks
Console.WriteLine(p.FirstName + " " + p.LastName); took (avg): 0ms - 683 ticks

А вот и код стенда:

Stopwatch s = new Stopwatch();

var p = new { FirstName = "Bill", LastName = "Gates" };

//First print to remove the initial cost
Console.WriteLine(p.FirstName + " " + p.LastName);
Console.WriteLine("{0} {1}", p.FirstName, p.LastName);

int n = 100000;
long fElapsedMilliseconds = 0, fElapsedTicks = 0, cElapsedMilliseconds = 0, cElapsedTicks = 0;

for (var i = 0; i < n; i++)
{
    s.Start();
    Console.WriteLine(p.FirstName + " " + p.LastName);
    s.Stop();
    cElapsedMilliseconds += s.ElapsedMilliseconds;
    cElapsedTicks += s.ElapsedTicks;
    s.Reset();
    s.Start();
    Console.WriteLine("{0} {1}", p.FirstName, p.LastName);
    s.Stop();
    fElapsedMilliseconds += s.ElapsedMilliseconds;
    fElapsedTicks += s.ElapsedTicks;
    s.Reset();
}

Console.Clear();

Console.WriteLine("Console.WriteLine(\"{0} {1}\", p.FirstName, p.LastName); took (avg): " + (fElapsedMilliseconds / n) + "ms - " + (fElapsedTicks / n) + " ticks");
Console.WriteLine("Console.WriteLine(p.FirstName + \" \" + p.LastName); took (avg): " + (cElapsedMilliseconds / n) + "ms - " + (cElapsedTicks / n) + " ticks");

Итак, я не знаю, чей ответ отметить как ответ :)

Хороший!

Только что добавлен

        s.Start();
        for (var i = 0; i < n; i++)
            result = string.Concat(p.FirstName, " ", p.LastName);
        s.Stop();
        ceElapsedMilliseconds = s.ElapsedMilliseconds;
        ceElapsedTicks = s.ElapsedTicks;
        s.Reset();

И это даже быстрее (я думаю, что string.Concat вызывается в обоих примерах, но первый требует какого-то перевода).

1000000 x result = string.Format("{0} {1}", p.FirstName, p.LastName); took: 249ms - 3571621 ticks
1000000 x result = (p.FirstName + " " + p.LastName); took: 65ms - 944948 ticks
1000000 x result = string.Concat(p.FirstName, " ", p.LastName); took: 54ms - 780524 ticks

Я поражен тем, что так много людей сразу же хотят найти код, который выполняется быстрее всего. Если ОДИН МИЛЛИОН итераций ВСЕ ЕЩЕ будет обрабатываться меньше секунды, будет ли это КАК-ЛИБО заметным для конечного пользователя? Маловероятно.

Преждевременная оптимизация = НЕИСПРАВНОСТЬ.

Я бы выбрал этот String.Format вариант только потому, что он имеет наибольший смысл с архитектурной точки зрения. Меня не волнует производительность, пока это не станет проблемой (и если бы это произошло, я бы спросил себя: нужно ли объединять миллион имен сразу? Конечно, все они не поместятся на экране ...)

Подумайте, захочет ли ваш клиент позже изменить его, чтобы он мог настроить, отображать ли он "Firstname Lastname" или "Lastname, Firstname." с помощью параметра «Формат», это легко - просто замените строку формата. С concat вам понадобится дополнительный код. Конечно, в данном конкретном примере это не кажется большим делом, но экстраполируйте.

Лучшим тестом было бы наблюдать за своей памятью с помощью Perfmon и счетчиков памяти CLR. Я понимаю, что вся причина, по которой вы хотите использовать String.Format вместо простого объединения строк, заключается в том, что поскольку строки неизменяемы, вы излишне обременяете сборщик мусора временными строками, которые необходимо восстановить на следующем проходе.

StringBuilder и String.Format, хотя потенциально медленнее, более эффективны с точки зрения памяти.

Что плохого в конкатенации строк?

Если вы имеете дело с чем-то, что должно быть легко читаемым (а это большая часть кода), я бы придерживался версии с перегрузкой оператора, ЕСЛИ НЕ:

  • Код нужно выполнить миллионы раз
  • Вы делаете тонны конкатов (более 4 - тонна)
  • Код нацелен на Compact Framework.

По крайней мере при двух из этих обстоятельств я бы использовал вместо этого StringBuilder.

Мне было любопытно, где находится StringBuilder с этими тестами. Результаты ниже ...

class Program {
   static void Main(string[] args) {

      var p = new { FirstName = "Bill", LastName = "Gates" };

      var tests = new[] {
         new { Name = "Concat", Action = new Action(delegate() { string x = p.FirstName + " " + p.LastName; }) },
         new { Name = "Format", Action = new Action(delegate() { string x = string.Format("{0} {1}", p.FirstName, p.LastName); }) },
         new { Name = "StringBuilder", Action = new Action(delegate() {
            StringBuilder sb = new StringBuilder();
            sb.Append(p.FirstName);
            sb.Append(" ");
            sb.Append(p.LastName);
            string x = sb.ToString();
         }) }
      };

      var Watch = new Stopwatch();
      foreach (var t in tests) {
         for (int i = 0; i < 5; i++) {
            Watch.Reset();
            long Elapsed = ElapsedTicks(t.Action, Watch, 10000);
            Console.WriteLine(string.Format("{0}: {1} ticks", t.Name, Elapsed.ToString()));
         }
      }
   }

   public static long ElapsedTicks(Action ActionDelg, Stopwatch Watch, int Iterations) {
      Watch.Start();
      for (int i = 0; i < Iterations; i++) {
         ActionDelg();
      }
      Watch.Stop();
      return Watch.ElapsedTicks / Iterations;
   }
}

Полученные результаты:

Concat: 406 тиков
Конкат: 356 тиков
Concat: 411 тиков
Concat: 299 тиков
Конкат: 266 тиков
Формат: 5269 тиков
Формат: 954 тика
Формат: 1004 тика
Формат: 984 тика
Формат: 974 тика
StringBuilder: 629 тиков
StringBuilder: 484 тика
StringBuilder: 482 тика
StringBuilder: 508 тиков
StringBuilder: 504 тика

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

Console.WriteLine("User {0} accessed {1} on {2}.", 
                   user.Name, fileName, timestamp);

вы понимаете смысл даже без имен переменных, тогда как concat загроможден кавычками и знаками + и сбивает меня с толку:

Console.WriteLine("User " + user.Name + " accessed " + fileName + 
                  " on " + timestamp + ".");

(Я позаимствовал пример Майка, потому что он мне нравится)

Если строка формата мало что значит без имен переменных, я должен использовать concat:

   Console.WriteLine("{0} {1}", p.FirstName, p.LastName);

Параметр формата заставляет меня читать имена переменных и сопоставлять их с соответствующими числами. Опция concat этого не требует. Меня все еще смущают кавычки и знаки +, но альтернатива хуже. Рубин?

   Console.WriteLine(p.FirstName + " " + p.LastName);

Что касается производительности, я ожидаю, что параметр формата будет медленнее, чем concat, поскольку формат требует, чтобы строка была проанализирована . Я не помню, чтобы мне приходилось оптимизировать такие инструкции, но если бы я это сделал, я бы посмотрел на string такие методы, как Concat() и Join() .

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

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

Жалко бедных переводчиков

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

Так что используйте string.Format(), особенно если вы собираетесь когда-нибудь разместить свое приложение там, где английский не является первым языком.

Поскольку я не думаю, что ответы здесь охватывают все, я хотел бы сделать здесь небольшое дополнение.

Console.WriteLine(string format, params object[] pars) звонки string.Format . Знак "+" означает конкатенацию строк. Я не думаю, что это всегда связано со стилем; Я стараюсь смешивать эти два стиля в зависимости от контекста, в котором я нахожусь.

Короткий ответ

Решение, с которым вы столкнулись, связано с распределением строк. Я постараюсь сделать это проще.

Скажите, что у вас есть

string s = a + "foo" + b;

Если вы выполните это, он будет оценивать следующим образом:

string tmp1 = a;
string tmp2 = "foo" 
string tmp3 = concat(tmp1, tmp2);
string tmp4 = b;
string s = concat(tmp3, tmp4);

tmp на самом деле это не локальная переменная, но она временная для JIT (помещается в стек IL). Если вы помещаете строку в стек (например, ldstr в IL для литералов), вы помещаете ссылку на указатель строки в стеке.

Момент, когда вы вызываете concat эту ссылку, становится проблемой, потому что нет доступной строковой ссылки, содержащей обе строки. Это означает, что .NET необходимо выделить новый блок памяти, а затем заполнить его двумя строками. Причина, по которой это является проблемой, заключается в том, что размещение является относительно дорогим.

При этом вопрос меняется на: Как уменьшить количество concat операций?

Итак, грубый ответ: string.Format для> 1 конкатенации '+' отлично подойдет для 1 конкатенации. И если вы не заботитесь об оптимизации микропроизводительности, string.Format в общем случае все будет отлично.

Заметка о культуре

А еще есть то, что называется культурой ...

string.Format позволяет использовать CultureInfo в вашем форматировании. Простой оператор "+" использует текущий язык и региональные параметры.

Это особенно важное замечание, если вы пишете форматы файлов и f.ex. double значения, которые вы «добавляете» к строке. На разных машинах вы можете получить разные строки, если не используете string.Format явный CultureInfo .

F.ex. подумайте, что произойдет, если вы измените "." для ',' при написании файла значений, разделенных запятыми ... на голландском языке десятичным разделителем является запятая, поэтому ваш пользователь может получить «забавный» сюрприз.

Более подробный ответ

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

Увеличение означает выделение нового блока памяти и копирование старых данных в новый буфер. После этого можно освободить старый блок памяти. На этом этапе вы понимаете: выращивание - дорогостоящая операция.

Наиболее практичный способ сделать это - использовать политику превышения доступности. Наиболее распространенной политикой является превышение доступности буферов со степенью 2. Конечно, вам нужно сделать это немного умнее, чем это (поскольку нет смысла увеличивать с 1,2,4,8, если вы уже знаете, что вам нужно 128 символов. ), но вы понимаете. Политика гарантирует, что вам не понадобится слишком много дорогостоящих операций, которые я описал выше.

StringBuilder - это класс, который в основном перераспределяет базовый буфер в степени двойки. string.Format использует StringBuilder под капотом.

Это делает ваше решение базовым компромиссом между избыточным выделением и добавлением (-множественным) (без культуры) или просто выделением и добавлением.