Wie viele String-Objekte werden bei Verwendung eines Pluszeichens erstellt?

115

Wie viele String-Objekte werden erstellt, wenn im folgenden Code ein Pluszeichen verwendet wird?

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

Wenn es wie folgt wäre, hätte ich drei String-Objekte gesagt: "1", "2", "12".

String result = "1" + "2";

Ich weiß auch, dass String-Objekte zur Verbesserung der Leistung im String Intern Pool / Table zwischengespeichert werden, aber das ist nicht die Frage.

Das Licht
quelle
Strings werden nur interniert, wenn Sie String.Intern explizit aufrufen.
Joe White
7
@ JoeWhite: sind sie?
Igor Korkhov
13
Nicht ganz. Alle String-Literale werden automatisch interniert. Die Ergebnisse von Zeichenfolgenoperationen sind nicht.
Stefan Paul Noack
Darüber hinaus gibt es im OP-Beispiel nur eine Zeichenfolgenkonstante, die interniert ist. Ich werde meine Antwort aktualisieren, um zu veranschaulichen.
Chris Shain
+1. Für ein reales Beispiel für die Notwendigkeit, eine Zeichenfolgenverkettung in diesem Stil zu codieren, enthält der Abschnitt "Beispiele" von msdn.microsoft.com/en-us/library/… eine, die nicht möglich wäre, wenn der Compiler sie nicht optimieren könnte aufgrund der Einschränkungen für Werte, die Attributparametern zugewiesen sind, auf eine einzelne Konstante.
ClickRick

Antworten:

161

Überraschenderweise kommt es darauf an.

Wenn Sie dies in einer Methode tun:

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

dann scheint der Compiler den Code mit der String.ConcatAntwort von @Joachim (+1 an ihn übrigens) auszugeben.

Wenn Sie sie als Konstanten definieren , z.

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

oder als Literale , wie in der ursprünglichen Frage:

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

dann optimiert der Compiler diese +Zeichen. Es ist äquivalent zu:

const String result = "1234";

Darüber hinaus entfernt der Compiler überflüssige konstante Ausdrücke und gibt sie nur aus, wenn sie verwendet oder verfügbar gemacht werden. Zum Beispiel dieses Programm:

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

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

Erzeugt nur eine Zeichenfolge - die Konstante result(gleich "1234"). oneund twoerscheinen nicht in der resultierenden IL.

Beachten Sie, dass zur Laufzeit möglicherweise weitere Optimierungen vorgenommen werden. Ich gehe nur nach dem, was IL produziert.

In Bezug auf die Internierung werden schließlich Konstanten und Literale interniert, aber der Wert, der interniert wird, ist der resultierende konstante Wert in der IL, nicht das Literal. Dies bedeutet, dass Sie möglicherweise noch weniger Zeichenfolgenobjekte als erwartet erhalten, da mehrere identisch definierte Konstanten oder Literale tatsächlich dasselbe Objekt sind! Dies wird durch Folgendes veranschaulicht:

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

In dem Fall, in dem Zeichenfolgen in einer Schleife (oder auf andere Weise dynamisch) verkettet werden, erhalten Sie eine zusätzliche Zeichenfolge pro Verkettung. Im Folgenden werden beispielsweise 12 Zeichenfolgeninstanzen erstellt: 2 Konstanten + 10 Iterationen, die jeweils zu einer neuen Zeichenfolgeninstanz führen:

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

Aber (auch überraschend) werden mehrere aufeinanderfolgende Verkettungen vom Compiler zu einer einzigen Verkettung mit mehreren Zeichenfolgen kombiniert. Zum Beispiel erzeugt dieses Programm auch nur 12 String-Instanzen! Dies liegt daran, dass " Selbst wenn Sie mehrere + -Operatoren in einer Anweisung verwenden, der Zeichenfolgeninhalt nur einmal kopiert wird. "

public class Program
{
    static void Main(string[] args)
    {
        string result = "";
        for (int i = 0; i < 10; i++)
            result += "a" + result;
        Console.ReadKey();
    }
}
Chris Shain
quelle
was ist mit String result = "1" + "2" + drei + vier; wobei zwei und drei wie String drei deklariert sind = "3"; String four = "4";?
Das Licht
Auch das ergibt eine Zeichenfolge. Ich habe es gerade durch LinqPad laufen lassen, um mich selbst zu überprüfen.
Chris Shain
1
@Servy - Der Kommentar scheint aktualisiert worden zu sein. Wenn Sie einen Kommentar ändern, wird er nicht als geändert markiert.
Sicherheitshund
1
Ein Fall, der der Vollständigkeit halber in Betracht gezogen werden sollte, ist die Verkettung in einer Schleife. ZB Wie viele String-Objekte weist der folgende Code zu:string s = ""; for (int i = 0; i < n; i++) s += "a";
Joren
1
Ich benutze LINQPad ( linqpad.net ) oder Reflector ( Reflector.net ). Ersteres zeigt Ihnen die IL von beliebigen Codeausschnitten, letzteres dekompiliert Assemblys in IL und kann aus dieser IL äquivalentes C # neu generieren. Es gibt auch ein integriertes Tool namens ILDASM ( msdn.microsoft.com/en-us/library/f7dy01k1(v=vs.80).aspx ) IL zu verstehen ist eine schwierige Sache - siehe codebetter.com/raymondlewallen/2005/ 02/07 /…
Chris Shain
85

Chris Shains Antwort ist sehr gut. Als die Person, die den Optimierer für die Verkettung von Zeichenfolgen geschrieben hat, möchte ich nur zwei weitere interessante Punkte hinzufügen.

Das erste ist, dass der Verkettungsoptimierer im Wesentlichen beide Klammern und die linke Assoziativität ignoriert, wenn dies sicher möglich ist. Angenommen, Sie haben eine Methode M (), die eine Zeichenfolge zurückgibt. Wenn du sagst:

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

dann begründet der Compiler, dass der Additionsoperator assoziativ bleibt, und daher ist dies dasselbe wie:

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

Aber dieses:

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

ist das gleiche wie

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

Das ist also die Verkettung der konstanten Zeichenfolge "CD" mit M().

Tatsächlich erkennt der Verkettungsoptimierer, dass die Verkettung von Zeichenfolgen assoziativ ist , und generiert String.Concat(M(), "AB")für das erste Beispiel, obwohl dies die linke Assoziativität verletzt.

Sie können dies sogar tun:

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

und wir werden immer noch generieren String.Concat(M(), "EF", M()).

Der zweite interessante Punkt ist, dass null und leere Zeichenfolgen weg optimiert werden. Wenn Sie dies tun:

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

Du wirst kriegen String.Concat(M(), M())

Dann stellt sich eine interessante Frage: Was ist damit?

string s = M() + null;

Wir können das nicht bis auf optimieren

string s = M();

da M()könnte null zurückgeben, String.Concat(M(), null)würde aber eine leere Zeichenfolge zurückgeben, wenn M()null zurückgegeben wird. Also reduzieren wir stattdessen

string s = M() + null;

zu

string s = M() ?? "";

Dadurch wird gezeigt, dass die Verkettung von Zeichenfolgen überhaupt nicht aufgerufen werden muss String.Concat.

Weitere Informationen zu diesem Thema finden Sie unter

Warum ist String.Concat nicht für StringBuilder.Append optimiert?

Eric Lippert
quelle
Ich denke, ein paar Fehler könnten da reingeschlichen sein. Sicherlich ("C" + "D") + M())erzeugt String.Concat("CD", M()), nicht String.Concat(M(), "AB"). Und weiter unten, (M() + "E") + (null + M())sollte erzeugen String.Concat(M(), "E", M()), nicht String.Concat(M(), M()).
Hammar
21
+1 für den Anfangsabsatz. :) Antworten wie diese überraschen mich immer wieder über Stack Overflow.
Brichins
23

Ich habe die Antwort bei MSDN gefunden. Einer.

Gewusst wie: Verketten mehrerer Zeichenfolgen (C # -Programmierhandbuch)

Bei der Verkettung wird eine Zeichenfolge an das Ende einer anderen Zeichenfolge angehängt. Wenn Sie Zeichenfolgenliterale oder Zeichenfolgenkonstanten mit dem Operator + verketten, erstellt der Compiler eine einzelne Zeichenfolge. Es tritt keine Laufzeitverkettung auf. Zeichenfolgenvariablen können jedoch nur zur Laufzeit verkettet werden. In diesem Fall sollten Sie die Auswirkungen der verschiedenen Ansätze auf die Leistung verstehen.

David
quelle
22

Nur einer. Der C # -Compiler faltet Zeichenfolgenkonstanten und kompiliert daher im Wesentlichen bis zu

String result = "1234";
JaredPar
quelle
Ich dachte, wann immer Sie "" verwenden, wird ein String-Objekt erstellt.
Das Licht
1
@ William im Allgemeinen ja. Aber ständiges Falten beseitigt die unnötigen Zwischenschritte
JaredPar
13

Ich bezweifle, dass dies durch einen Standard oder eine Spezifikation vorgeschrieben ist. Eine Version kann wahrscheinlich etwas anderes als eine andere.

Elende Variable
quelle
3
Zumindest für den C # -Compiler von Microsoft für VS 2008 und 2010 ist das Verhalten dokumentiert (siehe Antwort von @ David-Stratton). Das heißt, Sie haben Recht - soweit ich aus einer kurzen Durchsicht ersehen kann, gibt die C # -Spezifikation dies nicht an und sollte wahrscheinlich als Implementierungsdetail betrachtet werden.
Chris Shain