In .NET sind Zeichenfolgen unveränderlich und Variablen des Referenztyps. Dies ist oft eine Überraschung für neuere .NET-Entwickler, die sie aufgrund ihres Verhaltens für Werttyp-Objekte halten können. Anders als bei der Verwendung von StringBuilder
für lange Verkettung. in Schleifen, gibt es einen Grund in der Praxis, dass man diese Unterscheidung kennen muss?
Welchen Szenarios in der Praxis wird geholfen oder vorgebeugt, wenn Sie die Unterscheidung der Wertreferenz in Bezug auf .NET-Zeichenketten verstehen oder sie einfach als Werttypen vorgeben / missverstehen?
Das Design von string
s wurde bewusst so gewählt, dass Sie sich als Programmierer nicht allzu viele Sorgen machen sollten. In vielen Situationen bedeutet dies, dass Sie einfach Strings zuweisen, verschieben, kopieren und ändern können, ohne zu sehr an die möglichen komplizierten Konsequenzen zu denken, wenn ein anderer Verweis auf Ihren String existiert und gleichzeitig geändert wird (wie bei Objektverweisen).
(EDIT: dieser Abschnitt wurde später hinzugefügt)
Wenn Strings an eine Methode übergeben werden, werden sie als Referenz übergeben. Wenn sie nur im Methodenkörper gelesen werden, passiert nichts Besonderes. Wenn sie jedoch geändert werden, wird eine Kopie erstellt und die temporäre Variable wird im Rest der Methode verwendet. Dieser Prozess wird copy-on-write genannt.
Was Juniors Probleme bereitet, ist, dass sie daran gewöhnt sind, dass Objekte Referenzen sind und dass sie in einer Methode geändert werden, die den übergebenen Parameter ändert. Um dasselbe mit Strings zu tun, müssen sie das Schlüsselwort ref
verwenden. Dadurch kann die String-Referenz geändert und an die aufrufende Funktion zurückgegeben werden. Ist dies nicht der Fall, kann die Zeichenfolge nicht vom Methodenkörper geändert werden:
Diese Unterscheidung ist wichtig, aber Anfänger-Programmierer sind normalerweise besser dran, wenn sie nicht zu viel darüber wissen. Die Verwendung von StringBuilder
, wenn Sie viel "bauen" machen, ist gut, aber oft hat Ihre Anwendung viel mehr Fische zum Frittieren und der geringe Leistungsgewinn von StringBuilder
ist vernachlässigbar. Seien Sie vorsichtig bei Programmierern, die Ihnen sagen, dass all String-Manipulation mit StringBuilder durchgeführt werden sollte.
Als sehr grobe Faustregel: StringBuilder hat einige Erstellungskosten, aber das Anhängen ist billig. String hat einen geringen Erstellungskosten, aber die Verkettung ist relativ teuer. Der Wendepunkt liegt je nach Größe bei etwa 400-500 Verkettungen. Danach wird StringBuilder effizienter.
EDIT: basierend auf einem Kommentar von Konrad Rudolph, habe ich diesen Abschnitt hinzugefügt.
Wenn die vorherige Faustregel Sie wundern lässt, beachten Sie die folgenden etwas detaillierteren Erklärungen:
+
, StringBuilder.Append
, String.Format
, ToString
und andere Zeichenfolgenoperationen, wobei StringBuilder in solchen Fällen kaum effektiv ist. Also, wenn ist es effizient? In Fällen, in denen viele kleine Zeichenfolgen angehängt werden, d. H. Um Daten in eine Datei zu serialisieren, zum Beispiel wenn Sie die "geschriebenen" Daten, die einmal in StringBuilder "geschrieben" wurden, nicht ändern müssen. Und in Fällen, in denen viele Methoden etwas anhängen müssen, weil StringBuilder ein Referenztyp ist und Zeichenketten kopiert werden, wenn sie geändert werden.
Ein Problem steigt - nicht nur bei Junior-Programmierern - wenn sie versuchen, einen Referenzvergleich durchzuführen und herauszufinden, dass manchmal das Ergebnis in scheinbar gleichen Situationen wahr und manchmal falsch ist. Was ist passiert? Wenn die Zeichenfolgen vom Compiler interniert und dem globalen statischen internen Pool von Zeichenfolgen hinzugefügt wurden, kann der Vergleich zwischen zwei Zeichenfolgen auf dieselbe Speicheradresse verweisen. Wenn (Referenz!) Zwei gleiche Strings verglichen werden, eine internierte und eine nicht, wird false ergeben. Verwenden Sie =
comparison oder Equals
und spielen Sie nicht mit ReferenceEquals
herum, wenn Sie mit Strings arbeiten.
In derselben Liga passt ein seltsames Verhalten, das manchmal bei Verwendung von String.Empty
auftritt: Die statische String.Empty
wird immer interniert, eine Variable mit einem zugewiesenen Wert jedoch nicht. Standardmäßig weist der Compiler String.Empty
zu und verweist auf die gleiche Speicheradresse. Ergebnis: Eine veränderbare Zeichenfolgenvariable gibt im Vergleich zu ReferenceEquals
den Wert true zurück, während Sie stattdessen möglicherweise false erwarten.
Sie haben grundsätzlich gefragt, welche Situationen für Uneingeweihte eintreten können. Ich denke, mein Punkt läuft darauf hinaus, object.ReferenceEquals
zu vermeiden, weil es nicht vertrauenswürdig ist, wenn es mit Strings verwendet wird. Der Grund dafür ist, dass das String-Interning verwendet wird, wenn die Zeichenfolge im Code konstant ist, aber nicht immer. Sie können sich nicht auf dieses Verhalten verlassen. Obwohl String.Empty
und ""
immer intern sind, ist es nicht, wenn der Compiler glaubt, dass der Wert änderbar ist.Verschiedene Optimierungsoptionen (debug vs release und andere) führen zu unterschiedlichen Ergebnissen.
Wenn Sie tun brauchen Sie trotzdem ReferenceEquals
? Bei Objekten macht es Sinn, bei Strings dagegen nicht. Unterrichten Sie jeden, der mit Strings arbeitet, um seine Verwendung zu vermeiden, es sei denn, sie verstehen auch unsafe
und fixierte Objekte.
Wenn Leistung wichtig ist, können Sie herausfinden, dass Strings tatsächlich nicht unveränderlich sind und dass mit StringBuilder
ist nicht immer der schnellste Ansatz .
Viele der Informationen, die ich hier verwendet habe, sind detailliert in diesem ausgezeichneten Artikel über Streicher , zusammen mit ein "How to" für die Manipulation von String in-Place (veränderbare Strings).
Update: Codebeispiel hinzugefügt
Update: Abschnitt 'in depth' hinzugefügt (hoffe, jemand findet das nützlich;)
Update: hinzugefügt einige Links, Abschnitt über String-Parameter hinzugefügt
Update: hinzugefügt Schätzung, wann von Strings zu stringbuilder wechseln
Update: nach einer Bemerkung von Konrad Rudolph einen zusätzlichen Abschnitt zur Leistung von StringBuilder vs String hinzugefügt
Eine unveränderliche Klasse verhält sich in allen üblichen Situationen wie ein Werttyp, und Sie können ziemlich viel programmieren, ohne sich viel um den Unterschied zu kümmern.
Wenn Sie etwas tiefer graben und sich um die Leistung kümmern, haben Sie einen echten Nutzen für die Unterscheidung. Zum Beispiel um zu wissen, dass, obwohl das Übergeben eines Strings als Parameter an eine Methode so wirkt, als ob eine Kopie des Strings erstellt würde, das Kopieren nicht tatsächlich stattfindet. Dies könnte eine Überraschung für Leute sein, die an Sprachen gewöhnt sind, in denen Strings tatsächlich Werttypen sind (wie VB6?), Und viele Strings übergeben, da Parameter für die Leistung nicht gut wären.
String ist eine besondere Rasse. Sie sind Referenztypen, die von den meisten Programmierern immer noch als Wertetyp verwendet werden. Indem es unveränderlich gemacht wird und der interne Pool verwendet wird, optimiert es die Speichernutzung, die sehr groß sein wird, wenn es ein reiner Werttyp ist.
Weitere Lesarten hier:
C # .NET String Objekt ist wirklich als Referenz? auf SO
String.Intern Methode auf MSDN < br> Zeichenfolge (C # -Referenz) in MSDN
Aktualisierung:
Bitte beachten Sie den Kommentar von abel
zu diesem Beitrag. Es hat meine irreführende Aussage korrigiert.
Tags und Links string .net pass-by-value pass-by-reference