Скільки об'єктів String буде створено при використанні знака плюс?


115

Скільки об'єктів String буде створено при використанні знака плюс у наведеному нижче коді?

String result = "1" + "2" + "3" + "4";

Якби це було нижче, я б сказав три об’єкти String: "1", "2", "12".

String result = "1" + "2";

Я також знаю, що об'єкти String є кешованими в басейні / таблиці String Intern для поліпшення продуктивності, але це не питання.


Рядки інтерновані лише у тому випадку, якщо ви явно викликаєте String.Intern.
Джо Вайт

7
@JoeWhite: вони?
Ігор Корхов

13
Не зовсім. Усі літеральні рядки інтерновані автоматично. Результатів рядкових операцій немає.
Стефан Пол Ноак

Більше того, у прикладі OP є лише одна струнна константа, і вона інтернована. Я оновлю свою відповідь для ілюстрації.
Кріс Шайн

+1. Для прикладу з реального життя необхідності кодування строкової катенації у цьому стилі, розділ Приклади msdn.microsoft.com/en-us/library/… має такий варіант, який був би неможливим, якби компілятор не зміг його оптимізувати. до єдиної постійної через обмеження значень, призначених параметрам атрибутів.
ClickRick

Відповіді:


161

Дивно, але це залежить.

Якщо ви робите це методом:

void Foo() {
    String one = "1";
    String two = "2";
    String result = one + two + "34";
    Console.Out.WriteLine(result);
}

тоді компілятор, здається String.Concat, видає код, використовуючи відповідь @Joachim (+1 до нього btw).

Якщо ви визначаєте їх як константи , наприклад:

const String one = "1";
const String two = "2";
const String result = one + two + "34";

або в прямому сенсі , як в оригінальному запитанні:

String result = "1" + "2" + "3" + "4";

тоді компілятор оптимізує ці +знаки. Це еквівалентно:

const String result = "1234";

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

const String one = "1";
const String two = "1";
const String result = one + two + "34";

public static void main(string[] args) {
    Console.Out.WriteLine(result);
}

Генерує лише один рядок - константа result(дорівнює "1234"). oneі twoне відображаються в отриманому ІР.

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

Нарешті, що стосується інтернування, константи та буквали є інтернованими, але значення, яке інтерновано, є отриманим постійним значенням в ІЛ, а не буквальним. Це означає, що ви можете отримати ще менше рядкових об'єктів, ніж ви очікували, оскільки кілька однаково визначених констант або літералів будуть фактично одним і тим же об’єктом! Це пояснюється наступним:

public class Program
{
    private const String one = "1";
    private const String two = "2";
    private const String RESULT = one + two + "34";

    static String MakeIt()
    {
        return "1" + "2" + "3" + "4";
    }   

    static void Main(string[] args)
    {
        string result = "1" + "2" + "34";

        // Prints "True"
        Console.Out.WriteLine(Object.ReferenceEquals(result, MakeIt()));

        // Prints "True" also
        Console.Out.WriteLine(Object.ReferenceEquals(result, RESULT));
        Console.ReadKey();
    }
}

У випадку, коли рядки об'єднані в цикл (або іншим чином динамічно), ви отримуєте один додатковий рядок за конкатенацію. Наприклад, наступне створює 12 рядкових екземплярів: 2 константи + 10 ітерацій, кожен з яких призводить до нового екземпляра String:

public class Program
{
    static void Main(string[] args)
    {
        string result = "";
        for (int i = 0; i < 10; i++)
            result += "a";
        Console.ReadKey();
    }
}

Але (що також дивно), кілька послідовних конкатенацій компілюється компілятором у єдине багаторядкове конкатенацію. Наприклад, ця програма також виробляє лише 12 рядкових екземплярів! Це тому, що " Навіть якщо ви використовуєте кілька операторів + в одному операторі, вміст рядка копіюється лише один раз ".

public class Program
{
    static void Main(string[] args)
    {
        string result = "";
        for (int i = 0; i < 10; i++)
            result += "a" + result;
        Console.ReadKey();
    }
}

як щодо результату String = "1" + "2" + три + чотири; де два і три оголошуються як рядок три = "3"; Рядок чотири = "4" ;?
Світло

Навіть це призводить до одного рядка. Я просто пробіг його через LinqPad, щоб перевірити себе.
Кріс Шайн

1
@Servy - коментар, схоже, був оновлений. Коли ви змінюєте коментар, він не позначається як змінений.
Охорона безпеки

1
Один випадок, який непогано було б врахувати для повноти, - це об'єднання в цикл. Наприклад, скільки рядкових об'єктів виділяє наступний код:string s = ""; for (int i = 0; i < n; i++) s += "a";
Joren

1
Я використовую LINQPad ( linqpad.net ) або Reflector ( reflector.net ). Перший показує вам ІР довільних фрагментів коду, другий декомпілює збірки в IL і може повторно генерувати еквівалентний C # з цього IL. Існує також вбудований інструмент під назвою ILDASM ( msdn.microsoft.com/en-us/library/f7dy01k1(v=vs.80).aspx ) Розуміння ІЛ - хитра штука
Кріс Шайн

85

Відповідь Кріса Шейна дуже хороша. Як людина, яка написала оптимізатор конкатенації рядків, я просто додам два додаткових цікавих пункту.

Перший полягає в тому, що оптимізатор конкатенації по суті ігнорує і круглі дужки, і ліву асоціативність, коли це може зробити це безпечно. Припустимо, у вас є метод M (), який повертає рядок. Якщо ви говорите:

string s = M() + "A" + "B";

тоді компілятор міркує, що оператор додавання залишається асоціативним, і тому це те саме, що:

string s = ((M() + "A") + "B");

Але це:

string s = "C" + "D" + M();

те саме, що

string s = (("C" + "D") + M());

так що це конкатенація постійної рядка "CD" з M().

Фактично, оптимізатор конкатенації розуміє, що конкатенація рядків є асоціативним і генерує String.Concat(M(), "AB")для першого прикладу, навіть якщо це порушує ліву асоціативність.

Ви навіть можете це зробити:

string s = (M() + "E") + ("F" + M()));

і ми все ще будемо генерувати String.Concat(M(), "EF", M()).

Другий цікавий момент - оптимізовані нульові та порожні рядки. Тож якщо ви це зробите:

string s = (M() + "") + (null + M());

ти отримаєш String.Concat(M(), M())

Тоді виникає цікаве запитання: що з цим?

string s = M() + null;

Ми не можемо оптимізувати це до

string s = M();

тому що M()може повернути null, але String.Concat(M(), null)поверне порожню рядок, якщо return M()null. Тож те, що ми робимо, це замість зменшення

string s = M() + null;

до

string s = M() ?? "";

Тим самим демонструючи, що з'єднання рядків не потрібно насправді викликати String.Concatвзагалі.

Для подальшого читання з цього приводу див

Чому String.Concat не оптимізовано до StringBuilder.Append?


Я думаю, що там можуть проскочити кілька помилок. Звичайно, ("C" + "D") + M())породжує String.Concat("CD", M()), ні String.Concat(M(), "AB"). І далі вниз, (M() + "E") + (null + M())повинен генерувати String.Concat(M(), "E", M()), а не String.Concat(M(), M()).
хаммар

21
+1 для початкового абзацу. :) Такі відповіді - це те, що мене завжди дивує щодо переповнення стека.
бричін

23

Я знайшов відповідь у MSDN. Один.

Як: об'єднати кілька рядків (Посібник з програмування C #)

Конкатенація - це процес додавання одного рядка до кінця іншого рядка. Коли ви об'єднуєте рядкові літерали або рядкові константи за допомогою оператора +, компілятор створює одну рядок. Зв'язок часу виконання не відбувається. Однак струнні змінні можна об'єднати лише під час виконання. У цьому випадку слід розуміти наслідки різних підходів для продуктивності.


22

Тільки один. Компілятор C # складе рядкові константи і, отже, по суті компілює

String result = "1234";

Я думав, що коли ви використовуєте "", він створює об'єкт String.
Світло

1
@William взагалі так. Але постійне згортання видалить непотрібні проміжні кроки
JaredPar

13

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


3
Це задокументоване поведінка принаймні для компілятора C # Microsoft для VS 2008 та 2010 років (див. Відповідь Девіда-Страттона). При цьому ви маєте рацію - наскільки я можу сказати з швидкого ознайомлення, специфікація C # не вказує цього, і це, мабуть, слід розглядати як деталі реалізації.
Кріс Шайн

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