Допустим, вы хотите выводить или объединять строки. Какой из следующих стилей вы предпочитаете?
var p = new { FirstName = "Bill", LastName = "Gates" };
Console.WriteLine("{0} {1}", p.FirstName, p.LastName);
Console.WriteLine(p.FirstName + " " + p.LastName);
Вы предпочитаете использовать формат или просто объединяете строки? Что твое любимое? У кого-то из них болят глаза?
Есть ли у вас какие-нибудь рациональные аргументы в пользу того, чтобы использовать одно, а не другое?
Я бы выбрал второй.





Лично второй, поскольку все, что вы используете, находится в прямом порядке, в котором оно будет выводиться. В то время как с первым вы должны сопоставить {0} и {1} с правильной переменной, что легко испортить.
По крайней мере, это не так плохо, как C++ sprintf, где, если вы неправильно укажете тип переменной, все это взорвется.
Кроме того, поскольку второй полностью встроен и ему не нужно выполнять поиск и замену всех элементов {0}, последний должен быть быстрее ... хотя я не знаю наверняка.
Я думаю, это сильно зависит от сложности вывода. Я склонен выбирать тот сценарий, который лучше всего работает в данный момент.
Подберите подходящий инструмент в зависимости от задачи: D Какой бы ни выглядел лучше!
Для очень простых манипуляций я бы использовал конкатенацию, но как только вы выйдете за пределы 2 или 3 элементов, формат станет более подходящим IMO.
Еще одна причина предпочесть String.Format заключается в том, что строки .NET неизменяемы и таким образом создается меньше временных / промежуточных копий.
Я предпочитаю и второй вариант, но в настоящее время у меня нет рациональных аргументов в поддержку этой позиции.
Обычно я предпочитаю первое, так как особенно когда строки становятся длинными, их становится намного легче читать.
Другое преимущество, я считаю, заключается в производительности, поскольку последний фактически выполняет 2 оператора создания строки перед передачей последней строки в метод Console.Write. Я считаю, что String.Format использует StringBuilder под обложками, поэтому множественные конкатенации избегаются.
Однако следует отметить, что если параметры, которые вы передаете в String.Format (и другие подобные методы, такие как Console.Write), являются типами значений, то они будут упакованы перед передачей, что может привести к снижению производительности. Сообщение в блоге об этом здесь.
Для базовой конкатенации строк я обычно использую второй стиль - более легкий для чтения и более простой. Однако, если я делаю более сложную комбинацию строк, я обычно выбираю 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 + ".");
Сохранено всего несколько символов, но я думаю, что в этом примере формат делает его намного чище.
Объединение строк прекрасно в таком простом сценарии - это сложнее с чем-то более сложным, чем это, даже с 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);
Мне действительно нравится первый, потому что, когда в тексте смешано много переменных, мне кажется, что его легче читать. Кроме того, с кавычками легче работать при использовании 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 мс, поэтому я также выводю тики таймера.
Но, конечно, вам следует выполнить операцию более одного раза, чтобы получить измерения.
И потерять вызов Console.Writeline (), поскольку это выходит за рамки вопроса?
вы пробовали строить струны? ;)
Я бы использовал String.Format, но у меня также была бы строка формата в файлах ресурсов, чтобы ее можно было локализовать для других языков. Использование простого конкатенации строк не позволяет вам этого сделать. Очевидно, что если вам никогда не понадобится локализовать эту строку, это не повод для размышлений. Это действительно зависит от того, для чего предназначена строка.
Если это будет показано пользователю, я бы использовал String.Format, чтобы я мог локализовать, если мне нужно - и FxCop проверит его орфографию для меня, на всякий случай :)
Если он содержит числа или любые другие нестроковые вещи (например, даты), я бы использовал String.Format, потому что он дает мне больше контроль над форматированием.
Если это для создания запроса типа SQL, я бы использовал Linq.
Если для объединения строк внутри цикла, я бы использовал StringBuilder, чтобы избежать проблем с производительностью.
Если это для какого-то вывода, который пользователь не увидит и не будет влиять на производительность, я бы использовал String.Format, потому что у меня есть привычка использовать его в любом случае, и я просто к этому привык :)
Я всегда шел по маршруту string.Format (). Возможность хранить форматы в переменных, как в примере Натана, является большим преимуществом. В некоторых случаях я могу добавить переменную, но после того, как объединяется более одной переменной, я рефакторирую, чтобы использовать форматирование.
Да, и просто для полноты, следующее на несколько тиков быстрее, чем обычная конкатенация:
Console.WriteLine(String.Concat(p.FirstName," ",p.LastName));
О боже, после прочтения одного из других ответов я попытался изменить порядок операций - поэтому сначала выполняю конкатенацию, а затем 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!») Перед первой операцией возвращает все времена в соответствие.
Затем полностью удалите Console.WriteLine из ваших тестов. Это искажает результаты!
Я всегда начинаю с одноразового или «контрольного» сценария при запуске тестов производительности именно по этой причине.
На самом деле, я проводил эти тесты вчера, но было уже поздно, поэтому я не отправлял свои ответы.
Суть в том, что в среднем они занимают и то, и другое одинаковое время. Я провел тест более 100000 итераций.
Я тоже попробую с StringBuilder, и я отправлю код и результаты, когда вернусь домой.
Вот мои результаты более 100000 итераций:
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");
Итак, я не знаю, чей ответ отметить как ответ :)
Почему у этого ответа синий фон?
@yossi синий, потому что отвечающий такой же, как и спрашивающий
Попробуйте этот код.
Это слегка измененная версия вашего кода.
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); took: 618ms - 2213706 ticks
1000000 x result = (p.FirstName + " " + p.LastName); took: 166ms - 595610 ticks
Очень интересно. Я получил в среднем 224 мс против 48 мс, улучшение x4,66, даже лучше, чем у вашего x3,72. Интересно, есть ли инструмент посткомпиляции, который может переписать IL string.Format, который не использует никаких функций составного форматирования (то есть просто {0}), и заменить их значительно более быстрой конкатенацией строк. Интересно, что такой подвиг достижим с помощью существующего перезаписчика IL, такого как PostSharp.
Строки неизменяемы, это означает, что в вашем коде снова и снова используется один и тот же крошечный кусок памяти. Добавление одних и тех же двух строк вместе и создание одной и той же новой строки снова и снова не влияет на память. .Net достаточно умен, чтобы использовать ту же ссылку на память. Поэтому ваш код на самом деле не проверяет разницу между двумя методами concat. Смотрите код в моем ответе ниже.
Честно говоря, я всегда конкатенирую, так как мне легче читать и вау, это быстрее :)
Значит, скорость - единственная причина выбрать одно из них?
Хороший!
Только что добавленное
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.Concat(...). Это делается во время компиляции, поэтому не влияет на производительность во время выполнения. Если вы запустите свои тесты несколько раз или запустите их на больших тестовых образцах, вы увидите, что они идентичны.
Мне больше нравится первый (формат). Это более читабельно, и вы не создаете дополнительных временных строковых объектов.
Я поражен тем, что так много людей сразу же хотят найти код, который выполняется быстрее всего. Если ОДИН МИЛЛИОН итераций ВСЕ ЕЩЕ будет обрабатываться менее секунды, будет ли это КАК-ЛИБО заметным для конечного пользователя? Маловероятно.
Premature optimization = FAIL.
Я бы выбрал вариант String.Format только потому, что он имеет наибольший смысл с архитектурной точки зрения. Меня не волнует производительность, пока это не станет проблемой (и если бы это случилось, я бы спросил себя: нужно ли объединять миллион имен сразу? Конечно, все они не поместятся на экране ...)
Подумайте, не захочет ли ваш клиент позже изменить его, чтобы он мог настроить отображение "Firstname Lastname" или "Lastname, Firstname.". С помощью параметра «Формат» это легко - просто замените строку формата. С concat вам понадобится дополнительный код. Конечно, в данном конкретном примере это не кажется большим делом, но экстраполируйте.
Хороший момент с точки зрения «Преждевременная оптимизация == НЕУДАЧА», да. Но когда вы начинаете платить за следы выполнения (облако и инфраструктура как услуга, кто-нибудь?) И / или начинаете поддерживать 1 миллион пользователей в чем-то, тогда вопрос не в ответе отдельному пользователю на запрос. Стоимость обслуживания запроса для пользователя - это затраты для вашей чистой прибыли, а также проблема масштаба, если / когда поступают еще несколько тысяч вызовов ...
Это совершенно неправильно. В среде веб-разработки часто код генерации строк находится глубоко в вашей модели, представлениях и контроллерах и может вызываться десятки тысяч раз при загрузке страницы. Сокращение времени, затрачиваемого на оценку кода генерации строк, на 50% может быть огромной победой.
Я согласен с преждевременной оптимизацией = НЕУДАЧИ. Однако существует неопределенный порог между хорошей работой и преждевременной оптимизацией. Чтобы объяснить мою идею, если кто-то планирует обработать 2 миллиарда строк, то важна каждая миллисекунда. Но с другой стороны, если это просто напечатать сообщение, которое вряд ли будет использоваться, тогда не стоит тратить время на принятие решения.
Подобный вопрос применим не только к одному экземпляру OP. Ответ - это такая вещь, которую люди могут запомнить как «каким способом я должен собирать струны?» поскольку они пишут все своего кода.
@Benjamin: ... в этом случае вы должны профилировать и обнаружить, что это ваше узкое место. Я готов поспорить, что вы просто берете это из ниоткуда; Написав и профилировав ряд веб-приложений в прошлом, я почти всегда обнаруживал, что узким местом в времени ответа (на стороне сервера) являются запросы к базе данных.
@ BlueRaja-DannyPflughoeft определенно не вытягивает его из ниоткуда. У сверхбыстрого веб-сайта запросы к БД не будут блокировать веб-запросы http от пользователей, а вместо этого будут читать из кешей в памяти. Прочитать highscalability.com
Это определенно НЕ преждевременная оптимизация. Совершенно заблуждение. Производительность строки может полностью замедлить работу пользовательского интерфейса, особенно в .NET, если вы много форматируете и строите строки. ubiquity.acm.org/article.cfm?id=1513451
Я бы согласился с вами, если бы мне не приходилось иметь дело с проблемой производительности, напрямую связанной с использованием string.format (). работа с бизнес-приложением, загружающим данные, которое использовало много вызовов string.format (), потому что это было легко, но при работе с более чем 1000 одновременных пользователей 20 вызовов string.format на странице складывались быстро
@FredrikKalseth «Лучшая практика» - это не «преждевременная оптимизация». Поиск лучшего подхода к чему-то, чтобы сделать это привычкой, может сэкономить бесчисленные часы / дни и даже недели на устранение неполадок и оптимизацию постфактум. Это достаточно просто, чтобы принять его как «лучшую практику», не задумываясь над ней. Но я согласен с вашим справедливым предупреждением / мнением о том, что «преждевременная оптимизация = неудача».
Не существует такой вещи, как «преждевременная оптимизация» .... Код либо оптимизирован для решения поставленной задачи, либо нет ... Это просто предубеждение. Вы просто повторяете то, что слышали или читали в Интернете. На самом деле, на мой взгляд, девиз «преждевременная оптимизация» стал поводом для написания дрянного кода.
@Aidanapword Истинная производительность - это фактор, но архитектурные причины выбора даже не были затронуты в предыдущих ответах - их, по крайней мере, необходимо учитывать.
@Aidanapword, если у вас есть миллион пользователей на вашей облачной платформе, я надеюсь, что у вас также есть поток доходов, чтобы платить за эти несколько наносекунд на каждого пользователя. В противном случае я бы счел это неудачной бизнес-моделью. Стоимость за запрос или стоимость за пользователя здесь - единственное, что имеет значение.
Лучшим тестом было бы наблюдать за своей памятью с помощью Perfmon и счетчиков памяти CLR. Я понимаю, что вся причина, по которой вы хотите использовать String.Format вместо простого объединения строк, заключается в том, что поскольку строки неизменяемы, вы излишне обременяете сборщик мусора временными строками, которые необходимо восстановить на следующем проходе.
StringBuilder и String.Format, хотя потенциально медленнее, более эффективны с точки зрения памяти.
Что плохого в конкатенации строк?
Я согласен; каждая строковая операция создает новую копию строки. Рано или поздно сборщик мусора освободит всю эту память. Таким образом, выделение большого количества строк может снова вас укусить.
Если вы имеете дело с чем-то, что должно быть легко читаемым (а это большая часть кода), я бы придерживался версии с перегрузкой оператора, ЕСЛИ НЕ:
По крайней мере при двух из этих обстоятельств я бы использовал вместо этого 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 ticks Concat: 356 ticks Concat: 411 ticks Concat: 299 ticks Concat: 266 ticks Format: 5269 ticks Format: 954 ticks Format: 1004 ticks Format: 984 ticks Format: 974 ticks StringBuilder: 629 ticks StringBuilder: 484 ticks StringBuilder: 482 ticks StringBuilder: 508 ticks StringBuilder: 504 ticks
Выбираю исходя из читабельности. Я предпочитаю вариант формата, когда вокруг переменных есть текст. В этом примере:
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 имеет важное значение, потому что на разных естественных языках данные могут даже не располагаться в одном и том же порядке.
Согласно подготовительному материалу MCSD, Microsoft предлагает использовать оператор + при работе с очень небольшим количеством конкатенаций (вероятно, от 2 до 4). Я все еще не уверен, почему, но это нужно учитывать.
Жалко бедных переводчиков
Если вы знать ваше приложение останется на английском языке, тогда хорошо, сохраните тики часов. Однако во многих культурах, например, в адресах обычно встречается фамилия и имя.
Так что используйте string.Format(), особенно если вы собираетесь когда-либо размещать свое приложение там, где английский не является первым языком.
Как string.Format() будет вести себя по-разному в разных культурах? Не будет ли он по-прежнему печатать сначала имя, а затем фамилию? Похоже, вам придется принимать во внимание разную культуру в обеих ситуациях. Я чувствую, что здесь чего-то не хватает.
Я согласен с @DangerZone ... как string.Format() узнает, что вы используете имя в качестве адреса? Если бы string.Format() поменял местами {0} {1} на основе культуры, я бы счел его сломанным.
Я считаю, что Джереми пытался сделать вывод о том, что в описанном сценарии для поддержки разных стран может оказаться целесообразным извлечь саму строку формата в языковой ресурс. Для большинства стран эта строка будет "{0} {1}", но для стран, где сначала фамилия является типичной операцией (например, Венгрия, Гонконг, Камбоджа, Китай, Япония, Корея, Мадагаскар, Тайвань, Вьетнам и части Индии) этой строкой будет "{1} {0}".
В самом деле. Или, более тонко, добавьте строку формата как атрибут человека. Мне, например, нравится, когда моя фамилия стоит после имени, а моему коллеге Бэну нет.
Строки неизменяемы, это означает, что в вашем коде снова и снова используется один и тот же крошечный кусок памяти. Добавление одних и тех же двух строк вместе и создание одной и той же новой строки снова и снова не влияет на память. .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
Добавлен StringBuilder и образец вывода в ответ
Я вижу, как использование string.Format стоит здесь небольшого снижения производительности. С архитектурной точки зрения это лучше, так как это означает, что вы можете легко изменить формат. Но я действительно не вижу смысла в создании струнных инструментов. Каждый другой поток здесь говорит, что вы должны использовать Stringbuilder вместо конкатенации строк. В чем преимущество? Ясно, что не скорость, как показывает этот тест.
Поскольку я не думаю, что ответы здесь охватывают все, я хотел бы сделать здесь небольшое дополнение.
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 под капотом.
Это делает ваше решение базовым компромиссом между избыточным выделением и добавлением (-множественным) (без культуры) или просто выделением и добавлением.
Начиная с C# 6.0 для этого можно использовать интерполированные строки, что еще больше упрощает формат.
var name = "Bill";
var surname = "Gates";
MessageBox.Show($"Welcome to the show, {name} {surname}!");
An interpolated string expression looks like a template string that contains expressions. An interpolated string expression creates a string by replacing the contained expressions with the ToString represenations of the expressions’ results.
Интерполированные строки имеют производительность, аналогичную 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}";
Действительно, новый вариант C# 6.0 лучше предыдущих альтернатив - по крайней мере, с точки зрения удобочитаемости.
Верно. И это также безопаснее, так как вам не нужно беспокоиться о том, какой объект переходит к какому индексу (заполнителю), поскольку вы напрямую разместите объекты там, где хотите.
Кстати, он на самом деле вызывает Format (по крайней мере, с Roslyn).
Кстати, то, что имеет в виду этот плакат, называется «строковой интерполяцией» и адресовано в другом месте в этом потоке.
Это сообщение в блоге сейчас находится по адресу: jeffbarnes.net/blog/post/2006/08/08/…. Мне не хватает репутации для редактирования.