Виведення рядка: формат або конмат у 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); взяли: 618ms - 2213706 тиків
1000000 x result = (p.FirstName + "" + p.LastName); взяли: 166 мс - 595610 кліщів


1
Дуже цікаво. Я отримав в середньому 224 мс проти 48 мс, поліпшення x4,66, навіть краще, ніж ваш х3,72. Цікаво, чи існує інструмент після компіляції, який може переписати ІЛ string.Format, який не використовує жодних складених функцій форматування (тобто просто простих {0}) і замінить їх на значно більш швидке об'єднання рядків. Цікаво, що подібний подвиг досягається з існуючим переписувачем ІЛ, таким як PostSharp.
Аллон Гуралнек

31
Струни незмінні, це означає, що той самий крихітний фрагмент пам'яті використовується знову і знову у вашому коді. Додавання одних і тих же двох рядків разом і створення однієї і тієї ж нової рядки знову і знову не впливає на пам'ять. .Net досить розумний просто використовувати ту саму посилання на пам'ять. Тому ваш код не справді перевіряє різницю двох методів. Дивіться код у моїй відповіді нижче.
Ludington

1
Чесно кажучи, я завжди об'єднуюся, оскільки мені легше читати, і ось так швидше :)
puretppc

Тож швидкість є єдиною причиною вибору одного за іншим?
niico

158

Я вражений, що так багато людей відразу хочуть знайти код, який виконується найшвидше. Якщо ОДИН МІЛЬЙОН ітерацій ВИРОБНЕ зайняти менше секунди, чи це буде КОЖНЕ ШЛЯГО помітно для кінцевого користувача? Не дуже ймовірно.

Передчасна оптимізація = НЕПРАВНА.

Я б пішов з цим String.Formatваріантом лише тому, що це має найбільше сенс з архітектурної точки зору. Мене не хвилює вистава, поки це не стане проблемою (і якщо це сталося, я б запитав себе: чи потрібно мені об'єднати мільйон імен відразу? Напевно, вони не всі вмістяться на екрані ...)

Подумайте, чи згодом ваш клієнт хоче його змінити, щоб він міг налаштувати відображення "Firstname Lastname"або за "Lastname, Firstname."допомогою параметра Формат, це легко - просто поміняйте рядок формату. З конфатом вам знадобиться додатковий код. Впевнений, що в цьому конкретному прикладі не здається великою справою, а екстраполятом.


47
Хороший момент з точки зору "Передчасна оптимізація == FAIL", так. Але коли ви починаєте платити за виконання (хмара та інфраструктура як послуга, хтось?) Та / або починаєте підтримувати 1 мільйон користувачів на чомусь, тоді відповідь одному користувачу на запит не є питанням. Вартість обслуговування запиту для користувача - це вартість вашого нижнього рядка, а також масштабне питання, якщо / коли кілька інших тисяч дзвінків проходять через ...
Aidanapword

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

2
Таке запитання стосується не лише однієї інстанції ОП. Відповідь - це та річ, яку люди можуть запам’ятати як "в який спосіб я повинен збирати рядки?" як вони пишуть весь свій код.
Філ Міллер

6
@ Бенджамін: ... у такому випадку ви маєте профіль і визнаєте, що це ваше вузьке місце. Я б обміняв гроші, що ви просто витягаєте це з нізвідки; в минулому, написавши та профілювавши ряд веб-схем, я майже завжди знаходив вузьке місце у часи відповіді (на стороні сервера) як запити до бази даних.
BlueRaja - Danny Pflughoeft

2
Це, безумовно, НЕ передчасна оптимізація. Досить хибність. Продуктивність струн може повністю затримати інтерфейси користувача, особливо в .NET, якщо ви багато форматуєте і створюєте String. ubiquity.acm.org/article.cfm?id=1513451
користувач99999991

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.Writeline ("Почати!") Перед першою операцією приводить всі часи назад у відповідність.


2
Потім повністю видаліть Console.WriteLine зі своїх тестів. Це перекручування результатів!
CShark

Я завжди починаю зі сценарію «
викидання

36

Струни незмінні, це означає, що той самий крихітний фрагмент пам'яті використовується знову і знову у вашому коді. Додавання одних і тих же двох рядків разом і створення однієї і тієї ж нової рядки знову і знову не впливає на пам'ять. .Net досить розумний просто використовувати ту саму посилання на пам'ять. Тому ваш код не справді перевіряє різницю двох методів.

Спробуйте це для розміру:

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

1
Додано StringBuilder і зразок виводу у відповідь
mikeschuld

Я бачу, як string.Formatварто використовувати крихітний хіт продуктивності тут. Архітектурно це краще, оскільки це означає, що ви можете змінити формат легше. Але стрингбілдер я справді не бачу сенсу. Кожен інший потік тут говорить, що ви повинні використовувати Stringbuilder замість об'єднувальних рядків. Яка перевага? Очевидно, що не швидкість, як це доводить.
roryok

22

Шкода бідних перекладачів

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

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


2
Як би string.Format()поводитися по-різному в різних культурах? Невже він ще не надрукував би ім’я, а потім прізвище? Схоже, вам доведеться враховувати різну культуру в обох ситуаціях. Я відчуваю, що мені щось тут не вистачає.
Broots Waymb

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 є більш ефективними в пам'яті.

Що так погано в конкатенації рядків?


Я згоден; кожна операція рядка створює нову копію рядка. Вся ця пам’ять рано чи пізно буде повернена смітником. Тож виділення безлічі струн може пізніше повернутися до укусу.
Marnix van Valen

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.

До речі, він насправді називає формат (принаймні, з Рослін).
Філіпп

До речі, те, про що йдеться у цьому плакаті, називається "рядковою інтерполяцією", і адресовано в іншому місці цього потоку.
CShark

4
  1. Форматування - це спосіб .NET. Деякі інструменти рефакторингу (Refactor! Для одного) навіть запропонують перефактурувати код стислого стилю для використання стилю форматування.
  2. Форматування легше оптимізувати для компілятора (хоча, ймовірно, другий буде відновлено для використання швидкого методу "Concat").
  3. Форматування зазвичай зрозуміліше (особливо при форматному "форматі").
  4. Форматування означає неявні виклики до '.ToString' на всіх змінних, що добре для читабельності.
  5. Відповідно до "Ефективного C #", реалізація .NET 'WriteLine' і 'Format' змішана, вони автоматично встановлюють усі типи значень (що погано). "Ефективний C #" радить виконувати дзвінки ".ToString" явно, які IMHO є хибним (див. Публікацію Джеффа )
  6. Наразі підказки типу форматування не перевіряються компілятором, що призводить до помилок виконання. Однак це може бути змінено в наступних версіях.

4

Я вибираю на основі читабельності. Я вважаю за краще варіант форматування, коли навколо змінних є текст. У цьому прикладі:

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

ви розумієте значення навіть без змінних імен, тоді як конмат переповнюється цитатами та знаками + і бентежить мої очі:

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

(Я запозичив приклад Майка, тому що мені це подобається)

Якщо рядок формату не означає багато без імен змінних, я повинен використовувати concat:

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

Параметр форматування змушує мене читати імена змінних і відображати їх у відповідні числа. Конкретний варіант цього не вимагає. Мене все ще бентежить цитатами та знаками +, але альтернатива гірша. Рубі?

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

Я думаю, що параметр формату буде повільнішим, ніж стислість, оскільки для формату потрібно проаналізувати рядок . Я не пам'ятаю, щоб оптимізувати цей вид інструкцій, але якби я це зробив, я би stringроздивився такі методи як Concat()і Join().

Іншою перевагою формату є те, що рядок формату можна помістити у файл конфігурації. Дуже зручно з повідомленнями про помилки та текстом інтерфейсу користувача.


4

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

Якщо це буде показано користувачеві, я б використовував String.Format, щоб я міг локалізувати, якщо мені потрібно - і FxCop перевірить це для мене на всякий випадок :)

Якщо він містить номери або будь-які інші рядкові речі (наприклад, дати), я б використовував String.Format, оскільки це дає мені більше контролю над форматуванням .

Якщо для побудови запиту на зразок SQL, я б використав Linq .

Якщо для об'єднання рядків всередині циклу, я б використовував StringBuilder, щоб уникнути проблем з продуктивністю.

Якщо це для певного виходу, який користувач не побачить, і не збирається впливати на продуктивність, я б використовував String.Format, оскільки я звик користуватися ним у будь-якому випадку, і я просто звик до нього :)


3

Якщо ви маєте справу з чимось, що має бути легким для читання (а це найбільше код), я б дотримувався версії перевантаження оператора БЕЗКОШТОВНО:

  • Код потрібно виконати мільйони разів
  • Ви робите тонни присадок (більше 4 - це тонна)
  • Код орієнтований на Compact Framework

Принаймні за двома з цих обставин я б використовував 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під кришкою.

Це робить ваше рішення основним компромісом між надмірним розміщенням та додаванням (-мільти) (з / в культурою) або просто виділенням-додаванням.


1

Особисто, другий, як все, що ви використовуєте, знаходиться в прямому порядку, він буде виведений. Тоді як з першим вам потрібно зіставити {0} і {1} з належним var, який легко зіпсувати.

Принаймні, це не так вже й погано, як спринт C ++, де, якщо ви помилитесь типу змінної помилково, вся справа підірветься.

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


1

Насправді мені подобається перший, тому що, коли є багато змінних, що переплітаються з текстом, мені здається, що це легше читати. Плюс до цього, легше мати справу з цитатами при використанні формату string.Format (), uh. Ось гідний аналіз струнної конкатенації.


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.