Vorgegebene .NET-Zeichenfolgen sind Werttyp

7

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?

    
Dinah 02.11.2009, 00:11
quelle

4 Antworten

17

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).

String-Parameter in einem Methodenaufruf

(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:

%Vor%

Auf StringBuilder

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.

Mehr zu StringBuilder vs String-Leistung

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 mit vielen kleinen Strings hängt die String-Verkettung ziemlich schnell ab (30, 50 hängt an), aber bei 2μs ist sogar ein 100% iger Performance-Gewinn vernachlässigbar (sicher für einige seltene Situationen);
  • StringBuilder mit einigen großen String-Anhängen (80 Zeichen oder größere Strings) überspringt String-Verkettung nur nach Tausenden, manchmal Hunderttausender-Iterationen und der Unterschied ist oft nur ein paar Prozente;
  • Das Mischen von String-Aktionen (replace, insert, substring, regex usw.) macht oft die Verwendung von StringBuilder oder String-Verkettung gleich;
  • String-Verkettung von Konstanten kann vom Compiler, der CLR oder der JIT weg optimiert werden, nicht für StringBuilder;
  • Code mischt häufig die Verkettung + , 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.

Bei intern gespeicherten Strings

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.

Ein String.Empty

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.

%Vor%

In der Tiefe

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.

Leistung

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

    
Abel 02.11.2009, 00:32
quelle
3

Der einzige Unterschied, der für den meisten Code wirklich von Bedeutung ist, ist die Tatsache, dass null String-Variablen zugewiesen werden kann.

    
recursive 02.11.2009 00:20
quelle
3

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.

    
Guffa 02.11.2009 00:33
quelle
1

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.

    
o.k.w 02.11.2009 00:17
quelle