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


178

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

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

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

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

Вы предпочитаете использовать формат или просто соединяете строки? Что твое любимое? Один из них болит твои глаза?

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

Я бы пошел на второй.

Ответы:


88

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

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

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 result = string.Format ("{0} {1}", p.FirstName, p.LastName); потребовалось: 618мс - 2213706 тиков
1000000 x результат = (p.FirstName + "" + p.LastName); заняло: 166мс - 595610 тиков


1
Очень интересно. Я получил в среднем 224 мс против 48 мс, улучшение х4,66, даже лучше, чем у вас х3,72. Интересно, есть ли инструмент посткомпиляции, который может переписать IL string.Format, который не использует какие-либо составные функции форматирования (то есть просто простые {0}) и заменить их значительно более быстрой конкатенацией строк. Интересно, что такой подвиг достижим с существующим переписчиком IL, таким как PostSharp.
Аллон Гуралнек

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

1
Честно говоря, я всегда объединяюсь, потому что мне легче читать и вау, это быстрее :)
puretppc

То есть скорость - единственная причина, чтобы выбрать один над другим?
niico

158

Я поражен, что так много людей сразу хотят найти код, который выполняется быстрее всего. Если ОДИН МИЛЛИОН итераций ИСПОЛЬЗУЕТСЯ менее секунды, обрабатывается ли это ЛЮБОМ СПОСОБОМ, заметным для конечного пользователя? Не очень вероятно.

Преждевременная оптимизация = FAIL.

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

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


47
Хороший вопрос с точки зрения «преждевременной оптимизации == FAIL», да. Но когда вы начнете платить за выполнение (облако и инфраструктура как услуга, кто-нибудь?) И / или вы начнете поддерживать 1 миллион пользователей на что-то, ответ на запрос одного пользователя не будет вопросом. Стоимость обслуживания запроса для пользователя - это стоимость вашей нижней строки, а также проблема масштаба, если / когда поступают несколько других тысяч вызовов ...
Aidanapword

23
Это просто неправильно. В среде веб-разработки часто ваш код генерации строки будет глубоко в вашей модели, представлениях и контроллерах и может вызываться десятки тысяч раз за загрузку страницы. Сокращение времени, затрачиваемого на оценку кода генерации строк на 50%, может быть огромной победой.
Бенджамин Суссман

2
Подобный вопрос применим не только к одному экземпляру ОП. Ответ - это то, что люди могут вспомнить как «каким образом мне собирать струны?» как они пишут весь свой код.
Фил Миллер

6
@ Бенджамин: ... в этом случае, вы бы профилировать и найти это является вашим узким местом. Я бы поспорил на деньги, что вы просто тянете это из ниоткуда; написав и профилировав несколько веб-приложений в прошлом, я почти всегда находил узкое место во времени ответа (на стороне сервера) в запросах к базе данных.
BlueRaja - Дэнни Пфлугхофт

2
Это определенно НЕ преждевременная оптимизация. Совершенно ошибочно. Производительность строк может полностью затормозить пользовательский интерфейс, особенно в .NET, если вы много занимаетесь форматированием и сборкой строк. ubiquity.acm.org/article.cfm?id=1513451
user99999991

54

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


2
Затем полностью удалите Console.WriteLine из ваших тестов. Это искажает результаты!
CShark

Я всегда начинаю с одноразового или «контрольного» сценария при выполнении тестов производительности именно по этой причине
drzaus

36

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


Я вижу, как использование string.Formatстоит крошечной производительности здесь. Архитектурно это лучше, поскольку это означает, что вы можете изменить формат более легко. Но струнный строитель я действительно не вижу смысла. Любой другой поток здесь говорит, что вы должны использовать Stringbuilder вместо объединения строк. В чем преимущество? Очевидно, не скорость, как доказывает этот тест.
рёрок

22

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

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

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


2
Как string.Format()вести себя по-разному в разных культурах? Не будет ли по-прежнему печататься имя, затем фамилия? Похоже, что вам придется принимать во внимание различную культуру в обеих ситуациях. Я чувствую, что здесь что-то упущено.
Брутс Уэймб

2
Я согласен с @DangerZone .. как бы string.Format()знать, что вы используете имя для адреса? Если string.Format()поменять местами {0} {1}на основе культуры, я бы посчитал это сломанным.
Алекс Макмиллан

2
Я полагаю, что Джереми пытался доказать, что в описанном сценарии поддержки разных стран может быть целесообразным извлечь саму строку формата в языковой ресурс. Для большинства стран этой строкой будет "{0} {1}", но для стран, где первая фамилия является типичной операцией (например, Венгрия, Гонконг, Камбоджа, Китай, Япония, Корея, Мадагаскар, Тайвань, Вьетнам и части Индии) эта строка будет "{1} {0}" вместо этого.
Ричард Фостер

На самом деле. Или, более тонко, добавьте строку формата в качестве атрибута человека. Мне, например, нравится, когда моя фамилия носит мое имя, а мой коллега Бенг - нет.
Джереми МакГи

14

Вот мои результаты за 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");

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


Почему фон для этого ответа синий?
user88637

@yossi синее, потому что ответчик такой же, как и
спрашивающий

9

Конкатенация строк хороша в таком простом сценарии - она ​​сложнее с чем-либо более сложным, чем даже 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);

7

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

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


6

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

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

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


2
Но, конечно, вы должны выполнить операцию более одного раза, чтобы получить измерения.
erikkallen

2
И потерять вызов Console.Writeline (), поскольку это выходит за рамки вопроса?
Aidanapword

ты тестировал со строителем строк? ;)
niico

6

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

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

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

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

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

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


5

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

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


5

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

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

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


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

5

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

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

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


1
Это сообщение в блоге теперь находится по адресу: jeffbarnes.net/blog/post/2006/08/08/… . Я страдаю от недостатка репутации для редактирования.
Ричард Слейтер

5

Через неделю, 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)

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

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

Действительно, новый способ C # 6.0 лучше, чем предыдущие альтернативы - по крайней мере, с точки зрения читабельности.
Филипп

Это правильно. И это также безопаснее, так как вам не нужно беспокоиться о том, какой объект идет к какому индексу (заполнителю), поскольку вы будете непосредственно помещать объекты туда, где вы хотите, чтобы они были.
von v.

Кстати, это на самом деле вызывает формат (по крайней мере, с Roslyn).
Филипп

Кстати, то, на что ссылается этот постер, называется «интерполяция строк» ​​и рассматривается в другом месте этой темы.
CShark

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

4

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

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

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

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


4

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

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

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

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

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

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


3

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

  • Код должен быть выполнен миллионы раз
  • Вы делаете тонны конкатов (более 4 - это тонна)
  • Код ориентирован на Компактную платформу

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


3

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


2

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

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


2

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


2

Хороший!

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

        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

2
Это занимает ровно столько же времени, поскольку компиляция строк на основе оператора переводится компилятором в вызовы string.Concat(...). Это делается во время компиляции, поэтому не влияет на производительность во время выполнения. Если вы запускаете тесты несколько раз или запускаете тестовые образцы большего размера, вы увидите, что они идентичны.
Аллон Гуралнек

2

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

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под капотом.

Это делает ваше решение основным компромиссом между overallocate-and-append (-multiple) (w / wo culture) или просто «распределить-и-добавить».


1

Лично, второй, поскольку все, что вы используете, находится в прямом порядке, в котором он будет выводиться. Тогда как с первым вы должны сопоставить {0} и {1} с правильным var, что легко испортить.

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

Кроме того, поскольку второй является встроенным и не должен выполнять поиск и замену всех элементов {0}, последний должен быть быстрее ... хотя я точно не знаю.


1

Мне на самом деле нравится первый, потому что, когда в тексте много переменных, мне кажется, что их легче читать. Кроме того, легче использовать кавычки при использовании строки. Формат () форматирования. Вот достойный анализ конкатенации строк.


1

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


1

Да, и просто для полноты, следующее на несколько тиков быстрее обычной конкатенации:

Console.WriteLine(String.Concat(p.FirstName," ",p.LastName));

1

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


1

Мне было любопытно, где 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;
   }
}

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

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

1

Согласно подготовительному материалу MCSD, Microsoft предлагает использовать оператор + при работе с очень небольшим числом конкатенаций (вероятно, от 2 до 4). Я до сих пор не уверен, почему, но это то, что нужно учитывать.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.