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

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

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

Если бы это было так, я бы сказал три объекта String: "1", "2", "12".

String result = "1" + "2";

Я также знаю, что объекты String кэшируются в String Intern Pool/Table для повышения производительности, но это не вопрос.

Ответ 1

Удивительно, но это зависит.

Если вы сделаете это в методе:

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

то компилятор, кажется, испускает код с помощью String.Concat, как ответил @Joachim (+1 к нему кстати).

Если вы определяете их как константы, например:

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 не отображаются в полученном IL.

Имейте в виду, что во время выполнения могут быть дополнительные оптимизации. Я просто иду по тому, что ИЛ производится.

Наконец, что касается интернирования, то константы и литералы интернированы, но значение, которое интернировано, является конечным постоянным значением в IL, а не буквальным. Это означает, что вы можете получить меньшее количество строковых объектов, чем вы ожидаете, поскольку множество идентично определенных констант или литералов будут фактически одним и тем же объектом! Это иллюстрируется следующим:

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

Ответ 2

Ответ Криса Шаина очень хорош. Как человек, который написал оптимизатор конкатенации строк, я бы просто добавил еще два интересных момента.

Во-первых, оптимизатор конкатенации по существу игнорирует как круглые скобки, так и левую ассоциативность, когда он может сделать это безопасно. Предположим, что у вас есть метод 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) возвращает пустую строку, если M() возвращает значение null. Итак, что мы делаем, вместо этого уменьшаем

string s = M() + null;

to

string s = M() ?? "";

Таким образом, демонстрируя, что конкатенация строк вообще не требует вызова String.Concat.

Для дальнейшего чтения по этому вопросу см.

Почему String.Concat не оптимизирован для StringBuilder.Append?

Ответ 3

Я нашел ответ в MSDN. Один.

Как объединить несколько строк (руководство по программированию на С#)

Конкатенация - это процесс добавления одной строки в конец другая строка. Когда вы объединяете строковые литералы или строку константы с помощью оператора +, компилятор создает одиночный строка. Не происходит конкатенации времени выполнения. Однако строковые переменные могут быть объединены только во время выполнения. В этом случае вы должны понять последствия для различных подходов.

Ответ 4

Только один. Компилятор С# сбрасывает строковые константы и, следовательно, по существу компилируется до

String result = "1234";

Ответ 5

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

Если бы они были динамическими, они были бы оптимизированы для одного вызова String.Concat(строка, строка, строка, строка).

Ответ 6

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