Коли краще використовувати об'єднання String.Format vs string?


120

У мене є невеликий фрагмент коду, який аналізує значення індексу, щоб визначити вхід комірки в Excel. Мені це здається ...

Яка різниця між

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

і

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

Чи один "кращий", ніж інший? І чому?



Відповіді:


115

Перед C # 6

Якщо чесно, я вважаю, що перша версія є простішою, хоча я б спростив її до:

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

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

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

З C # 6

Інтерполяція рядків робить багато речей простішими для читання на C # 6. У цьому випадку ваш другий код стає:

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

який, мабуть, найкращий варіант, ІМО.



Я знаю, я знаю. Це було зроблено на жарт (прочитали посилання btw раніше, що було добре прочитано)
nawfal

Джон. Я завжди був фанатом містера Ріхтера і релігійно дотримувався вказівок щодо боксу тощо. Однак, прочитавши вашу (стару) статтю, я зараз конвертую. Спасибі
stevethethread

3
@ mbomb007: Зараз це за посиланням codeblog.jonskeet.uk/2008/10/08/…
Джон Скіт

4
Тепер, коли доступний C # 6, ви можете використовувати новий синтаксис інтерполяції рядків для того, що, на мою думку, є ще легшим для читання:xlsSheet.Write($"C{rowIndex}", null, title);
HotN

158

Моє первісне уподобання (походження від C ++ фону) було для String.Format. Я відмовився від цього пізніше з наступних причин:

  • З'єднання струн, мабуть, "безпечніше". Мені траплялося (і я бачив, як це трапляється з декількома іншими розробниками), щоб видалити параметр або зіпсувати порядок параметрів помилково. Компілятор не перевірятиме параметри щодо рядка формату, і ви виявите помилку виконання (тобто, якщо вам пощастить не мати його в незрозумілому методі, такому як реєстрація помилки). При конкатенації видалення параметра менш схильне до помилок. Ви можете стверджувати, що ймовірність помилки дуже мала, але це може статися.

- З'єднання рядків дозволяє мати нульові значення, String.Formatне робить. Написання " s1 + null + s2" не порушується, воно просто розглядає нульове значення як String.Empty. Ну, це може залежати від конкретного сценарію - бувають випадки, коли ви хочете помилку замість того, щоб мовчки ігнорувати нулеве перше ім’я. Однак навіть в цій ситуації я особисто вважаю за краще перевіряти наявність нулів і викидати конкретні помилки замість стандартного ArgumentNullException, який я отримую від String.Format.

  • З'єднання рядків працює краще. Деякі з вищезгаданих дописів уже згадують про це (не пояснюючи насправді чому, що обумовило мене написати цей пост :).

Ідея - компілятор .NET досить розумний для перетворення цього фрагмента коду:

public static string Test(string s1, int i2, int i3, int i4, 
        string s5, string s6, float f7, float f8)
{
    return s1 + " " + i2 + i3 + i4 + " ddd " + s5 + s6 + f7 + f8;
}

до цього:

public static string Test(string s1, int i2, int i3, int i4,
            string s5, string s6, float f7, float f8)
{
    return string.Concat(new object[] { s1, " ", i2, i3, i4, 
                    " ddd ", s5, s6, f7, f8 });
}

Що відбувається під кришкою String.Concat легко здогадатися (використовуйте Reflector). Об'єкти в масиві перетворюються на їх рядок через ToString (). Тоді обчислюється загальна довжина і виділяється лише одна рядок (із загальною довжиною). Нарешті, кожен рядок копіюється в отриманий рядок через wstrcpy в якомусь небезпечному фрагменті коду.

Причини String.Concat- це швидше? Що ж, ми можемо поглянути на те, що String.Formatробиться - ви здивуєтеся кількості коду, необхідного для обробки рядка формату. На додаток до цього (я бачив коментарі щодо споживання пам'яті), він String.Formatвикористовує внутрішньо StringBuilder. Ось як:

StringBuilder builder = new StringBuilder(format.Length + (args.Length * 8));

Таким чином, для кожного переданого аргументу він залишає 8 символів. Якщо аргумент є одноцифровим значенням, то дуже погано, у нас є витрачений простір. Якщо аргумент - це власний об’єкт, який повертає якийсь довгий текст ToString(), можливо, може знадобитися навіть перерозподіл (звичайно, найгірший сценарій).

Порівняно з цим, конкатенація витрачає лише простір об’єктного масиву (не надто багато, враховуючи це масив посилань). Немає розбору специфікаторів формату і немає посередника StringBuilder. Накладні витрати на бокс / розблокування є в обох методах.

Єдина причина, через яку я б зайшов String.Format - це локалізація. Введення рядків формату в ресурси дозволяє підтримувати різні мови, не псуючись з кодом (подумайте про сценарії, коли форматовані значення змінюють порядок залежно від мови, тобто "через {0} годин і {1} хвилин" на японській мові можуть виглядати зовсім інакше: ).


Підводячи підсумки мого першого (і досить довгого) повідомлення:

  • найкращий спосіб (з точки зору продуктивності та ремонтопридатності / читабельності) для мене - це використання рядкового конкатенації, без жодних ToString()викликів
  • якщо ви після виступу, ToString()телефонуйте самостійно, щоб уникнути боксу (я дещо упереджений до читабельності) - те саме, що і перший варіант у вашому запитанні
  • якщо ви показуєте користувачеві локалізовані рядки (тут справа не в тому випадку), String.Format()має край.

5
1) string.Formatє "безпечним" при використанні ReSharper; тобто він такий же безпечний, як і будь-який інший код, який можна [неправильно] використовувати. 2) string.Format чи дозволяє "безпечно" null: string.Format("A{0}B", (string)null)призводить до "АВ". 3) Мене рідко хвилює цей рівень продуктивності (і з цією метою це рідкісний день, коли я виходжу StringBuilder) ...

Погодьтесь 2), я відредагую пост. Не можу перевірити, чи було це безпечно ще в 1.1, але остання структура дійсно безпечна.
Ден C.

Чи використовується string.Concat, якщо один із операндів є викликом методу із зворотним значенням, а не параметром чи змінною?
Річард Коллетт

2
@RichardCollette Так, String.Concat використовується навіть для об'єднання зворотних значень викликів методу, наприклад, string s = "This " + MyMethod(arg) + " is a test";збирається для String.Concat()виклику в режимі випуску.
Дан К.

Фантастична відповідь; дуже добре написано і пояснено.
Франк V

6

Я думаю, що перший варіант є більш читабельним, і це має бути вашим головним питанням.

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

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

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



5

Для простого випадку, коли це проста одинарна конкатенація, я вважаю, що це не варто складності string.Format(і я не перевіряв, але я підозрюю, що для простого випадку, подібного до цього, string.Format може бути трохи повільніше, що з розбором рядків формату і все). Як і Джон Скіт, я вважаю за краще не чітко зателефонувати .ToString(), оскільки це буде зроблено неявно через string.Concat(string, object)перевантаження, і я думаю, що код чистіший і легше читати без нього.

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

Якщо в рядку формату є багато параметрів (знову ж таки, "багато" є суб'єктивним), я зазвичай вважаю за краще включати коментовані індекси в аргументи заміни, щоб не втратити значення, яке значення переходить до якого параметра. Надуманий приклад:

Console.WriteLine(
    "Dear {0} {1},\n\n" +

    "Our records indicate that your {2}, \"{3}\", is due for {4} {5} shots.\n" +
    "Please call our office at 1-900-382-5633 to make an appointment.\n\n" +

    "Thank you,\n" +
    "Eastern Veterinary",

    /*0*/client.Title,
    /*1*/client.LastName,
    /*2*/client.Pet.Animal,
    /*3*/client.Pet.Name,
    /*4*/client.Pet.Gender == Gender.Male ? "his" : "her",
    /*5*/client.Pet.Schedule[0]
);

Оновлення

Мені здається, що приклад, який я наводив, трохи заплутаний, бо, здається, я використовував і конкатенацію, і string.Formatтут. І так, логічно і лексично, це я і зробив. Але всі конкатенації будуть оптимізовані компілятором 1 , оскільки всі вони є рядковими літералами. Тож під час виконання буде одна струна. Тому я думаю, що я повинен сказати, що я волію уникати багатьох конкатенацій під час виконання .

Звичайно, більшість цієї теми зараз застаріла, якщо ви все ще не затримаєтесь на C # 5 або старіші. Зараз у нас є інтерпольовані рядки , які за читабельністю набагато перевершують string.Formatмайже у всіх випадках. У ці дні, якщо я просто не об'єдную значення безпосередньо на початок або кінець рядкового літералу, я майже завжди використовую інтерполяцію рядків. Сьогодні я б написав свій попередній приклад так:

Console.WriteLine(
    $"Dear {client.Title} {client.LastName},\n\n" +

    $"Our records indicate that your {client.Pet.Animal}, \"{client.Pet.Name}\", " +
    $"is due for {(client.Pet.Gender == Gender.Male ? "his" : "her")} " +
    $"{client.Pet.Schedule[0]} shots.\n" +
    "Please call our office at 1-900-382-5633 to make an appointment.\n\n" +

    "Thank you,\n" +
    "Eastern Veterinary"
);

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


1 Ви можете бачити це в специфікації C # :

... дозволено наступні конструкції в постійних виразах:

...

  • Попередньо визначений + ... двійковий оператор ...

Ви також можете перевірити це за допомогою невеликого коду:

const string s =
    "This compiles successfully, " +
    "and you can see that it will " +
    "all be one string (named `s`) " +
    "at run time";

1
fyi, ви можете використовувати @ "... багаторядковий рядок" замість усіх конкатенацій.
Аарон Палмер

Так, але тоді вам доведеться виправдовувати рядок зліва. @ рядки містять усі нові рядки та символи вкладки між цитатами.
P тато

Я знаю, що це старе, але це випадок, коли я б сказав, що покладіть рядок формату у файл resx.
Енді

2
Нічого собі, всі зосередилися на буквальному рядку, а не на серці.
P тато

heheh - Я щойно помітив струнну конкатенацію всередині вашогоString.Format()
Крістофер

3

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


3

Я переглянув String.Format (використовуючи Reflector), і він фактично створює StringBuilder, а потім викликає AppendFormat на ньому. Таким чином, це швидше, ніж конмат для декількох струмків. Найшвидшим (я вважаю) буде створення StringBuilder і здійснення дзвінків до Додавання вручну. Звичайно, кількість "багатьох" - для здогадки. Я б використовував + (насправді &, адже я здебільшого я програміст VB) для чогось такого простого, як ваш приклад. Оскільки це стає складніше, я використовую String.Format. Якщо є багато змінних, я б пішов на StringBuilder і Додав, наприклад, у нас є код, який будує код, там я використовую один рядок фактичного коду для виведення одного рядка згенерованого коду.

Здається, існує певна міркування щодо того, скільки рядків буде створено для кожної з цих операцій, тому давайте кілька простих прикладів.

"C" + rowIndex.ToString();

"C" - це вже рядок.
rowIndex.ToString () створює інший рядок. (@manohard - ніякого боксу rowIndex не відбудеться)
Тоді ми отримуємо остаточний рядок.
Якщо взяти приклад

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

тоді у нас є "C {0}", оскільки рядок
рядківIndex отримує коробку для передачі у функцію.
Створюється новий stringbuilder. AppendFormat викликається у
конструкторі струн - я не знаю деталей, як функціонує AppendFormat, але дозволяємо припустити, що це ультраефективний, все одно доведеться перетворити коробку rowIndex у рядок.
Потім конвертуйте програму stringbuilder у новий рядок.
Я знаю, що StringBuilders намагаються запобігти безглуздій копії пам'яті, але String.Format все ще закінчується додатковими накладними витратами порівняно з простою конкатенацією.

Якщо ми зараз візьмемо приклад з ще кількома рядками

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

ми маємо для початку 6 рядків, які будуть однаковими для всіх випадків.
Використовуючи конкатенацію, ми також маємо 4 проміжних рядка плюс кінцевий результат. Саме ті проміжні результати усуваються за допомогою String, Format (або StringBuilder).
Пам’ятайте, що для створення кожного проміжного рядка попередній повинен бути скопійований у нове місце пам’яті, потенційно повільним є не лише розподіл пам’яті.


4
Нітпік. У "a" + ... + "b" + ... + "c" + ... у вас насправді не буде 4 проміжних рядків. Компілятор генерує виклик статичного методу String.Concat (параметри string []), і всі вони будуть об'єднані одразу. Я все-таки віддаю перевагу string.Format заради читабельності, хоча.
P тато

2

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

Для з'єднань всередині циклів або великих рядків завжди слід намагатися використовувати клас StringBuilder.


2

Цей приклад, мабуть, занадто банальний, щоб помітити різницю. Насправді, я думаю, що в більшості випадків компілятор може оптимізувати будь-яку різницю взагалі.

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


1

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

тобто у мене є повідомлення "The user is not authorized for location " + locationабо "The User is not authorized for location {0}"

якщо я хотів колись змінити повідомлення, щоб сказати: location + " does not allow this User Access"або "{0} does not allow this User Access"

з string.Format, що мені потрібно зробити, це змінити рядок. для конкатенації я повинен змінити це повідомлення

якщо його використовувати в декількох місцях, це може заощадити багато часу.


1

У мене було враження, що string.format виявився швидшим, але, здається, це на 3 рази повільніше в цьому тесті

string concat = "";
        System.Diagnostics.Stopwatch sw1 = new System.Diagnostics.Stopwatch    ();
        sw1.Start();
        for (int i = 0; i < 10000000; i++)
        {
            concat = string.Format("{0}{1}{2}{3}{4}{5}{6}{7}{8}{9}{10}","1", "2" , "3" , "4" , "5" , "6" , "7" , "8" , "9" , "10" , i);
        }
        sw1.Stop();
        Response.Write("format: "  + sw1.ElapsedMilliseconds.ToString());
        System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch();
        sw2.Start();
        for (int i = 0; i < 10000000; i++)
        {
            concat = "1" + "2" + "3" + "4" + "5" + "6" + "7" + "8" + "9" + "10" + i;
        }
        sw2.Stop();

string.format зайняв 4,6 сек, а при використанні "+" знадобилося 1,6 сек.


7
Компілятор розпізнає "1" + "2" + "3" + "4" + "5" + "6" + "7" + "8" + "9" + "10"один буквений рядок, тому лінія ефективно стає тим, "12345678910" + iщо швидше попередньогоstring.Format(...)
wertzui

0

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


0

Я трохи профілював різні рядкові методи, включаючи string.Format, StringBuilder і concatenation string. З'єднання струн майже завжди перевершувало інші методи побудови рядків. Отже, якщо продуктивність є ключовою, то її краще. Однак, якщо продуктивність не є критичною, то я особисто знаходжу рядок. Формату буде простіше дотримуватися в коді. (Але це суб'єктивна причина) StringBuilder, мабуть, є найбільш ефективним щодо використання пам'яті.



-1

З'єднання рядків займає більше пам'яті в порівнянні з String.Format. Тож найкращим способом об'єднання рядків є використання String.Format або System.Text.StringBuilder Object.

Візьмемо перший випадок: "C" + rowIndex.ToString () Припустимо, rowIndex є типом значення, тому метод ToString () повинен встановити поле для перетворення значення в String, а потім CLR створює пам'ять для нової рядки з включеними обома значеннями.

Де як string.Format очікує параметр об'єкта і приймає rowIndex в якості об'єкта і перетворює його на внутрішній рядок offcourse, з'явиться Boxing, але це властиво, а також він не займе стільки пам'яті, як у першому випадку.

Для коротких рядків це не так важливо, я думаю ...

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.