Vorgeben .NET -Zeichenfolgen sind Werttyp
-
11-09-2019 - |
Frage
In .NET sind Zeichenfolgen unveränderlich und Referenztypvariablen. Dies ist für neuere .NET -Entwickler häufig eine Überraschung, die sie aufgrund ihres Verhaltens für Werttypobjekte verwechseln können. Anders als die Praxis der Verwendung StringBuilder
für eine lange Verkettung ESP. Gibt es in Schleifen einen Grund in der Praxis, dass man diese Unterscheidung kennen muss?
Welche realen Szenarien werden geholfen oder vermieden, indem sie die Wertschöpfungsunterscheidung in Bezug auf .NET-Saiten im Vergleich zu Verständnis/Missverständnis als Werttypen haben?
Lösung
Das Design von string
S war absichtlich so, dass Sie sich als Programmierer nicht zu viel Sorgen machen müssen. In vielen Situationen bedeutet dies, dass Sie einfach zu viel komplizierte Konsequenzen zuweisen, bewegen, kopieren, ändern, ohne zu viel zu denken, wenn eine andere Referenz auf Ihre Zeichenfolge vorhanden war und gleichzeitig geändert würde (wie bei Objektreferenzen).
String -Parameter in einem Methodenaufruf
(Bearbeiten: Dieser Abschnitt wurde später hinzugefügt)
Wenn Saiten an eine Methode weitergegeben werden, werden sie mit Referenz übergeben. Wenn sie nur im Methode -Körper gelesen werden, passiert nichts Besonderes. Wenn sie jedoch geändert werden, wird eine Kopie erstellt und die temporäre Variable im Rest der Methode verwendet. Dieser Prozess wird genannt Kopieren auf dem Schreiben.
Was Junioren beunruhigen, ist, dass sie daran gewöhnt sind, dass Objekte Referenzen sind und sie in einer Methode geändert werden, die den übergebenen Parameter ändert. Um dasselbe mit Saiten zu tun, müssen sie das verwenden ref
Stichwort. Dadurch kann die String -Referenz geändert und zur Aufruffunktion zurückgegeben werden. Wenn Sie dies nicht tun, kann die Zeichenfolge nicht durch den Method -Körper geändert werden:
void ChangeBad(string s) { s = "hello world"; }
void ChangeGood(ref string s) { s = "hello world"; }
// in calling method:
string s1 = "hi";
ChangeBad(s1); // s1 remains "hi" on return, this is often confusing
ChangeGood(ref s1); // s1 changes to "hello world" on return
Auf StringBuilder
Diese Unterscheidung ist wichtig, aber Anfängerprogrammierer sind normalerweise besser dran, nicht zu viel darüber zu wissen. Verwendung StringBuilder
Wenn Sie viel "Gebäude" durchführen, ist es gut, aber oft hat Ihre Bewerbung viel mehr Fische zum Braten und den kleinen Leistungsgewinn von StringBuilder
Ist vernachlässigbar. Seien Sie vorsichtig mit Programmierern, die Ihnen das sagen alle String Manipulation sollte mit StringBuilder durchgeführt werden.
In einer sehr groben Faustregel: StringBuilder hat einige Erstellungskosten, aber das Anhängen ist billig. Saite hat eine billige Kreationskosten, aber die Verkettung ist relativ teuer. Der Wendepunkt beträgt je nach Größe etwa 400-500 Verkettungen: Danach wird Stringbuilder effizienter.
Mehr zu StringBuilder vs String Performance
Bearbeiten: Basierend auf einem Kommentar von Konrad Rudolph habe ich diesen Abschnitt hinzugefügt.
Wenn Sie die vorherige Faustregel wundern lassen, sollten Sie die folgenden etwas detaillierteren Erklärungen betrachten:
- StringBuilder mit vielen kleinen Zeichenfolgen findet eine String -Verkettung ziemlich schnell (30, 50 Anhänge), aber bei 2 µs ist sogar 100% Leistungsgewinn oft vernachlässigbar (sicher für einige seltene Situationen).
- StringBuilder mit einigen großen String -Anhängen (80 Zeichen oder größere Saiten) überträgt die String -Verkettung erst nach Tausenden, manchmal Hundertstel von Tausenden Iterationen, und der Unterschied beträgt oft nur wenige Wahrnehmungen.
- Mischungsstringaktionen (ersetzen, einfügen, substring, regex usw.) häufig mit StringBuilder oder String -Verkettung gleich;
- Die String -Verkettung von Konstanten kann vom Compiler, dem CLR oder dem JIT optimiert werden, es kann nicht für StringBuilder;
- Code mischt oft Verkettung
+
,StringBuilder.Append
,String.Format
,ToString
und andere String -Operationen, die StringBuilder in solchen Fällen verwenden, ist kaum effektiv.
Also wann ist es effizient? In Fällen, in denen viele kleine Zeichenfolgen angehängt werden, dh beispielsweise Daten in einer Datei und wenn Sie die "geschriebenen" Daten einmal "geschrieben" an StringBuilder ändern müssen. Und in Fällen, in denen viele Methoden etwas anhängen müssen, da StringBuilder ein Referenztyp ist und Zeichenfolgen kopiert werden, wenn sie geändert werden.
Auf interned Saiten
Ein Problem steigt - nicht nur mit Junior -Programmierern -, wenn sie versuchen, einen Referenzvergleich durchzuführen und herauszufinden, dass das Ergebnis manchmal wahr ist, und manchmal in den gleichen Situationen falsch ist. Was ist passiert? Wenn die Saiten vom Compiler interniert wurden und dem globalen statischen internierten Pool von Saiten hinzugefügt wurden, kann der Vergleich zwischen zwei Zeichenfolgen auf dieselbe Speicheradresse hinweisen. Wenn (Referenz!) Zwei gleiche Zeichenfolgen, einer Praktikant und einer, vergleicht, ergibt sich falsch. Verwenden =
Vergleich, oder Equals
und spiele nicht mit ReferenceEquals
Beim Umgang mit Saiten.
Auf String.Empty
In derselben Liga passt zu einem seltsamen Verhalten, das manchmal beim Gebrauch auftritt String.Empty
: die statische String.Empty
ist immer interniert, aber eine Variable mit einem zugewiesenen Wert ist nicht. Standardmäßig wird der Compiler jedoch zugewiesen String.Empty
und verweisen Sie auf die gleiche Speicheradresse. Ergebnis: Eine veränderliche Zeichenfolgevariable im Vergleich zu ReferenceEquals
, kehrt wahr, während Sie stattdessen möglicherweise false erwarten.
// emptiness is treated differently:
string empty1 = String.Empty;
string empty2 = "";
string nonEmpty1 = "something";
string nonEmpty2 = "something";
// yields false (debug) true (release)
bool compareNonEmpty = object.ReferenceEquals(nonEmpty1, nonEmpty2);
// yields true (debug) false (release, depends on .NET version and how it's assigned)
bool compareEmpty = object.ReferenceEquals(empty1, empty2);
Ausführlich
Sie haben im Grunde gefragt, welche Situationen der Uneingeweihten auftreten können. Ich denke, mein Punkt läuft auf das Vermeiden hinaus object.ReferenceEquals
Weil es nicht vertrauenswürdig werden kann, wenn es mit Saiten verwendet wird. Der Grund dafür ist, dass die String -Einbringer 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 ""
sind immer interniert, es ist nicht der Fall, wenn der Compiler der Ansicht ist, dass der Wert veränderlich ist. Unterschiedliche Optimierungsoptionen (Debug vs Release und andere) führen zu unterschiedlichen Ergebnissen.
Wann tun du brauchst ReferenceEquals
ohnehin? Bei Objekten macht es Sinn, aber mit Saiten nicht. Bringen Sie jedem mit Strings bei, um seine Verwendung zu vermeiden, es sei denn, er versteht auch unsafe
und festgesteckte Objekte.
Leistung
Wenn die Leistung wichtig ist, können Sie feststellen, dass die Saiten tatsächlich sind nicht unveränderlich und das Verwendung StringBuilder
ist nicht Immer der schnellste Ansatz.
Viele der hier verwendeten Informationen sind detailliert In diesem ausgezeichneten Artikel über Strings, zusammen mit einem "Wie man" zum Manipulieren von Saite In-Place (veränderliche Zeichenfolgen).
Aktualisieren: Code -Beispiel hinzugefügt
Aktualisieren: Hinzufügen von Abschnitt "Tiefe" (hoffe, jemand findet das nützlich;)
Aktualisieren: Einige Links fügten hinzu, fügte Abschnitt zu String -Params hinzu
Aktualisieren: Die Schätzung für den Wechsel von Strings zu StringBuilder hinzugefügt, um zu wechseln
Aktualisieren: Nach einer Bemerkung von Konrad Rudolph einen zusätzlichen Abschnitt über StringBuilder vs String -Leistung hinzugefügt
Andere Tipps
Die einzige Unterscheidung, die für die meisten Code wirklich wichtig ist, ist die Tatsache, dass null
Kann String -Variablen zugewiesen werden.
Eine unveränderliche Klasse wirkt wie ein Werttyp in allen gemeinsamen Situationen, und Sie können eine Menge Programmierung machen, ohne sich viel um den Unterschied zu kümmern.
Wenn Sie ein wenig tiefer graben und sich um die Leistung kümmern, die Sie für die Unterscheidung echt verwenden. Zum Beispiel, um zu wissen, dass das Übergeben einer Zeichenfolge als Parameter an eine Methode so wirkt, als ob eine Kopie der Zeichenfolge erstellt wird, findet das Kopieren nicht tatsächlich statt. Dies könnte eine Überraschung für Menschen sein, die an Sprachen gewöhnt sind, in denen Zeichenfolgen tatsächlich Werttypen sind (wie VB6?), Und viele Saiten als Parameter nicht gut für die Leistung zu sein.
Saite ist eine besondere Rasse. Sie sind Referenztypen, aber von den meisten Codierern als Werttyp verwendet. Indem es es unveränderlich macht und den Praktikumspool verwendet, optimiert es die Speicherverbrauch, die bei einem reinen Werttyp enorm sein wird.
Weitere Lesungen hier:
C# .NET String -Objekt ist wirklich durch Referenz? auf so
String.Intern -Methode auf msdn
String (C# Referenz) auf MSDN
Aktualisieren:
Bitte beziehen Sie sich auf abel
'S Kommentar zu diesem Beitrag. Es korrigierte meine irreführende Aussage.