wake-up-neo.net

Warum ist String in C # ein Referenztyp, der sich wie ein Werttyp verhält?

Ein String ist ein Referenztyp, obwohl er die meisten Merkmale eines Werttyps aufweist, z. B. unveränderlich und überladen, um den Text zu vergleichen, anstatt sicherzustellen, dass er auf dasselbe Objekt verweist.

Warum ist string dann nicht nur ein Werttyp?

340
Davy8

Zeichenfolgen sind keine Werttypen, da sie sehr umfangreich sein können und auf dem Heap gespeichert werden müssen. Werttypen werden (in allen Implementierungen der CLR bis jetzt) ​​auf dem Stack gespeichert. Die Stapelzuweisung von Zeichenfolgen würde viele Probleme verursachen: Der Stapel ist nur 1 MB für 32-Bit und 4 MB für 64-Bit. Sie müssten jede Zeichenfolge mit einer Kopierstrafe kästchen, Sie könnten keine internen Zeichenfolgen verwenden und benötigen Speicherplatz würde aufsteigen, etc ...

(Bearbeiten: Es wurde eine Klarstellung hinzugefügt, dass die Speicherung von Werttypen ein Implementierungsdetail ist. Dies führt dazu, dass wir einen Typ mit Wertsematiken haben, der nicht von System.ValueType erbt. Danke Ben.

311
codekaizen

Es handelt sich nicht um einen Wertetyp, da die Leistung (Raum und Zeit!) Fürchterlich wäre, wenn es sich um einen Wertetyp handeln würde und der Wert jedes Mal kopiert werden muss, wenn er an Methoden übergeben und von diesen zurückgegeben wird.

Es hat eine Wertesemantik, um die Welt gesund zu halten. Können Sie sich vorstellen, wie schwierig es wäre, wenn

string s = "hello";
string t = "hello";
bool b = (s == t);

b auf false setzen? Stellen Sie sich vor, wie schwierig das Codieren für jede Anwendung wäre.

54
jason

Die Unterscheidung zwischen Referenztypen und Werttypen ist im Grunde ein Leistungskompromiss bei der Gestaltung der Sprache. Referenztypen haben einen gewissen Overhead bei der Erstellung und Zerstörung sowie der Speicherbereinigung, da sie auf dem Heap erstellt werden. Werttypen haben hingegen Overhead bei Methodenaufrufen (wenn die Datengröße größer als ein Zeiger ist), da das gesamte Objekt kopiert wird und nicht nur ein Zeiger. Da Zeichenfolgen viel größer als die Größe eines Zeigers sein können (und normalerweise sind), werden sie als Referenztypen entworfen. Wie Servy ausführte, muss die Größe eines Wertetyps zur Kompilierungszeit bekannt sein, was bei Zeichenfolgen nicht immer der Fall ist.

Die Frage der Wandlungsfähigkeit ist ein separates Thema. Sowohl Referenztypen als auch Werttypen können entweder veränderlich oder unveränderlich sein. Werttypen sind jedoch normalerweise unveränderlich, da die Semantik für veränderbare Werttypen verwirrend sein kann.

Referenztypen sind in der Regel veränderlich, können jedoch als unveränderlich ausgelegt werden, wenn dies sinnvoll ist. Zeichenfolgen werden als unveränderlich definiert, da sie bestimmte Optimierungen ermöglichen. Wenn beispielsweise dasselbe Zeichenfolgenliteral im selben Programm mehrmals vorkommt (was durchaus üblich ist), kann der Compiler dasselbe Objekt wiederverwenden.

Warum ist "==" also überladen, um Zeichenfolgen nach Text zu vergleichen? Weil es die nützlichste Semantik ist. Wenn zwei Zeichenfolgen im Text gleich sind, können sie aufgrund der Optimierungen dieselbe Objektreferenz sein oder auch nicht. Das Vergleichen von Referenzen ist also ziemlich nutzlos, während das Vergleichen von Text fast immer das ist, was Sie wollen.

Allgemein gesprochen hat Strings das, was als Wertsemantik bezeichnet wird. Dies ist ein allgemeineres Konzept als Werttypen, bei dem es sich um ein C # -spezifisches Implementierungsdetail handelt. Werttypen haben eine Wertsemantik, Referenztypen können jedoch auch eine Wertsemantik haben. Wenn ein Typ eine Wertsemantik hat, können Sie nicht wirklich feststellen, ob die zugrunde liegende Implementierung ein Referenztyp oder ein Werttyp ist, sodass Sie dies als Implementierungsdetail betrachten können.

25
JacquesB

Dies ist eine späte Antwort auf eine alte Frage, aber bei allen anderen Antworten fehlt der Punkt, nämlich, dass .NET bis .NET 2.0 im Jahr 2005 keine Generika hatte.

String ist ein Referenztyp anstelle eines Wertetyps, da es für Microsoft von entscheidender Bedeutung war, sicherzustellen, dass Zeichenfolgen in nicht generischen Auflistungen auf die effizienteste Weise gespeichert werden können, z System.Collection.ArrayList.

Das Speichern eines Wertetyps in einer nicht generischen Sammlung erfordert eine spezielle Konvertierung in den Typ object, der als Boxing bezeichnet wird. Wenn die CLR einen Werttyp einfügt, wird der Wert in einen System.Object Eingeschlossen und auf dem verwalteten Heap gespeichert.

Das Lesen des Werts aus der Auflistung erfordert die umgekehrte Operation, die als Unboxing bezeichnet wird.

Sowohl Boxen als auch Unboxing verursachen nicht zu vernachlässigende Kosten: Boxen erfordert eine zusätzliche Zuordnung, Unboxing erfordert eine Typprüfung.

Einige Antworten behaupten fälschlicherweise, dass string niemals als Werttyp implementiert werden konnte, da seine Größe variabel ist. Tatsächlich ist es einfach, Zeichenfolgen mit einer Strategie zur Optimierung kleiner Zeichenfolgen als Datenstruktur fester Länge zu implementieren: Zeichenfolgen werden direkt als Folge von Unicode-Zeichen im Speicher gespeichert, mit Ausnahme großer Zeichenfolgen, die als Zeiger auf einen externen Puffer gespeichert werden. Beide Darstellungen können so gestaltet werden, dass sie dieselbe feste Länge haben, d. H. Die Größe eines Zeigers.

Wenn es vom ersten Tag an Generics gegeben hätte, wäre es wahrscheinlich eine bessere Lösung gewesen, String als Werttyp zu haben, mit einer einfacheren Semantik, einer besseren Speichernutzung und einer besseren Cache-Lokalität. Ein List<string>, Der nur kleine Zeichenketten enthält, könnte ein einzelner zusammenhängender Speicherblock sein.

13
ZunTzu

Nicht nur Strings sind unveränderliche Referenztypen. Auch Multicast-Delegierte. Deshalb ist das Schreiben sicher

protected void OnMyEventHandler()
{
     delegate handler = this.MyEventHandler;
     if (null != handler)
     {
        handler(this, new EventArgs());
     }
}

Ich nehme an, dass Zeichenfolgen unveränderlich sind, da dies die sicherste Methode ist, mit ihnen zu arbeiten und Speicher zuzuweisen. Warum sind sie keine Werttypen? Vorherige Autoren haben Recht mit der Stapelgröße usw. Ich möchte auch hinzufügen, dass das Festlegen von Zeichenfolgen als Referenztypen das Speichern der Baugruppengröße ermöglicht, wenn Sie dieselbe konstante Zeichenfolge im Programm verwenden. Wenn Sie definieren

string s1 = "my string";
//some code here
string s2 = "my string";

Möglicherweise werden beide Instanzen der Konstante "my string" in Ihrer Assembly nur einmal zugewiesen.

Wenn Sie Zeichenfolgen wie gewohnt verwalten möchten, platzieren Sie die Zeichenfolge in einem neuen StringBuilder (Zeichenfolgen). Oder verwenden Sie MemoryStreams.

Wenn Sie eine Bibliothek erstellen möchten, in der große Zeichenfolgen in Ihren Funktionen übergeben werden sollen, definieren Sie einen Parameter entweder als StringBuilder oder als Stream.

8
Bogdan_Ch

Außerdem wird festgelegt, wie Zeichenfolgen implementiert werden (je nach Plattform unterschiedlich) und wann Sie mit dem Zusammenfügen beginnen. Wie mit einem StringBuilder. Es weist Ihnen einen Puffer zu, in den Sie kopieren können, sobald Sie das Ende erreicht haben, und es weist Ihnen noch mehr Speicher zu, in der Hoffnung, dass bei einer großen Verkettung die Leistung nicht beeinträchtigt wird.

Vielleicht kann Jon Skeet hier oben helfen?

6
Chris

Es ist hauptsächlich ein Leistungsproblem.

Wenn sich Strings wie ein Wertetyp verhalten, hilft dies beim Schreiben von Code, aber wenn es sich um einen Wertetyp handelt, wird die Leistung erheblich beeinträchtigt.

Werfen Sie einen Blick in die Tiefe und lesen Sie Nice article zu Zeichenfolgen im .net-Framework.

5
Denis Troller

Wie können Sie feststellen, dass string ein Referenztyp ist? Ich bin nicht sicher, ob es darauf ankommt, wie es implementiert wird. Strings in C # sind genau unveränderlich, sodass Sie sich über dieses Problem keine Gedanken machen müssen.

2

Tatsächlich haben Strings nur sehr wenige Ähnlichkeiten mit Werttypen. Für den Anfang sind nicht alle Werttypen unveränderlich. Sie können den Wert eines Int32 nach Belieben ändern und es wäre immer noch die gleiche Adresse auf dem Stapel.

Zeichenfolgen sind aus einem sehr guten Grund unveränderlich. Sie haben nichts damit zu tun, dass es sich um einen Referenztyp handelt, sondern haben viel mit der Speicherverwaltung zu tun. Es ist nur effizienter, ein neues Objekt zu erstellen, wenn sich die Zeichenfolgengröße ändert, als Dinge auf dem verwalteten Heap zu verschieben. Ich denke, Sie mischen Wert-/Referenztypen und Konzepte für unveränderliche Objekte.

Was "==" betrifft: Wie Sie bereits sagten, ist "==" eine Überladung von Operatoren und wurde aus einem sehr guten Grund implementiert, um das Framework beim Arbeiten mit Zeichenfolgen nützlicher zu machen.

2
WebMatrix

In einfachen Worten kann jeder Wert, der eine bestimmte Größe hat, als Werttyp behandelt werden.

2
saurav.net

Ist nicht so einfach, wie Strings aus Zeichen-Arrays bestehen. Ich betrachte Strings als Character Arrays []. Daher befinden sie sich auf dem Heap, da der Referenzspeicherort auf dem Stapel gespeichert ist und auf den Anfang des Speicherorts des Arrays auf dem Heap verweist. Die Stringgröße ist nicht bekannt, bevor sie zugewiesen wurde ... perfekt für den Heap.

Das ist der Grund, warum ein String wirklich unveränderlich ist, da der Compiler das nicht weiß, wenn Sie ihn ändern, auch wenn er dieselbe Größe hat, und ein neues Array zuweisen und den Positionen im Array Zeichen zuweisen muss. Es ist sinnvoll, wenn Sie sich Strings so vorstellen, dass Sprachen Sie davor schützen, Speicher im laufenden Betrieb zuweisen zu müssen (lesen Sie C wie beim Programmieren).

1
BionicCyborg