Wenn ich die Diskussionen hier auf SO verfolge, habe ich schon mehrmals die Bemerkung gelesen, dass mutable structs "böse" sind (wie in der Antwort auf diese Frage ).
Was ist das eigentliche Problem mit Mutability und Structs in C#?
Wenn ich die Diskussionen hier auf SO verfolge, habe ich schon mehrmals die Bemerkung gelesen, dass mutable structs "böse" sind (wie in der Antwort auf diese Frage ).
Was ist das eigentliche Problem mit Mutability und Structs in C#?
Strukturen sind Werttypen, was bedeutet, dass sie kopiert werden, wenn sie weitergegeben werden.
Wenn Sie also eine Kopie ändern, ändern Sie nur diese Kopie, nicht das Original und auch keine anderen Kopien, die möglicherweise vorhanden sind.
Wenn Ihre Struktur unveränderlich ist, dann sind alle automatischen Kopien, die aus der Übergabe von Werten resultieren, gleich.
Wenn Sie sie ändern wollen, müssen Sie dies bewusst tun, indem Sie eine neue Instanz der Struktur mit den geänderten Daten erstellen. (nicht eine Kopie)
Wo soll ich anfangen ;-p
Eric Lippert's Blog ist immer für ein Angebot gut:
Dies ist ein weiterer Grund, warum veränderbare Werttypen böse sind. Versuchen Sie immer Wertetypen unveränderlich zu machen.
Erstens neigt man dazu, Änderungen leicht zu verlieren... zum Beispiel, wenn man Dinge aus einer Liste herausnimmt:
Foo foo = list[0];
foo.Name = "abc";
Was hat sich dadurch geändert? Nichts Nützliches...
Dasselbe gilt für Immobilien:
myObj.SomeProperty.Size = 22; // the compiler spots this one
die Sie dazu zwingen:
Bar bar = myObj.SomeProperty;
bar.Size = 22;
myObj.SomeProperty = bar;
weniger kritisch ist das Größenproblem; veränderbare Objekte pflegen mehrere Eigenschaften zu haben; wenn Sie jedoch eine Struktur mit zwei int
s, a string
, a DateTime
und eine bool
können Sie sehr schnell eine Menge Speicherplatz verbrauchen. Bei einer Klasse können sich mehrere Aufrufer eine Referenz auf dieselbe Instanz teilen (Referenzen sind klein).
Ich würde nicht sagen, dass böse aber Mutabilität ist oft ein Zeichen für den Übereifer des Programmierers, ein Maximum an Funktionalität zu bieten. In der Realität ist dies oft nicht nötig, und das wiederum macht die Schnittstelle kleiner, einfacher zu benutzen und schwieriger falsch zu benutzen (= robuster).
Ein Beispiel hierfür sind Lese-/Schreib- und Schreib-/Schreibkonflikte in Race Conditions. Diese können in unveränderlichen Strukturen einfach nicht auftreten, da ein Schreibvorgang keine gültige Operation ist.
Außerdem behaupte ich, dass Veränderbarkeit wird fast nie wirklich benötigt der Programmierer einfach denkt dass es könnte in der Zukunft sein. Es macht zum Beispiel einfach keinen Sinn, ein Datum zu ändern. Erstellen Sie stattdessen ein neues Datum auf der Grundlage des alten. Dies ist ein billiger Vorgang, so dass die Leistung keine Rolle spielt.
Veränderliche Strukturen sind nicht böse.
Sie sind unter Hochleistungsbedingungen absolut notwendig. Zum Beispiel, wenn Cache-Zeilen oder die Garbage Collection zu einem Engpass werden.
Ich würde die Verwendung einer unveränderlichen Struktur in diesen vollkommen gültigen Anwendungsfällen nicht als "böse" bezeichnen.
Ich kann den Punkt sehen, dass die Syntax von C# nicht hilft, den Zugriff auf ein Mitglied eines Werttyps oder eines Referenztyps zu unterscheiden, daher bin ich für bevorzugt unveränderliche Strukturen, die Unveränderlichkeit erzwingen, gegenüber veränderlichen Strukturen.
Anstatt jedoch unveränderliche Strukturen einfach als "böse" zu bezeichnen, würde ich dazu raten, die Sprache anzunehmen und sich für hilfreichere und konstruktivere Daumenregeln einzusetzen.
Zum Beispiel: "structs sind Werttypen, die standardmäßig kopiert werden. Sie brauchen eine Referenz, wenn Sie sie nicht kopieren wollen. または "Versuchen Sie zuerst, mit schreibgeschützten Strukturen zu arbeiten" .
Strukturen mit öffentlichen veränderbaren Feldern oder Eigenschaften sind nicht schlecht.
Struct-Methoden (im Gegensatz zu Property Settern), die "this" verändern, sind etwas böse, nur weil .net keine Möglichkeit bietet, sie von Methoden zu unterscheiden, die dies nicht tun. Struktur-Methoden, die "this" nicht verändern, sollten auch bei Nur-Lese-Strukturen aufrufbar sein, ohne dass ein defensives Kopieren erforderlich ist. Methoden, die "this" mutieren, sollten auf Nur-Lese-Strukturen überhaupt nicht aufrufbar sein. Da .net nicht verbieten will, dass struct-Methoden, die "this" nicht verändern, auf Nur-Lese-Strukturen aufgerufen werden, aber auch nicht zulassen will, dass Nur-Lese-Strukturen mutiert werden, kopiert es defensiv Strukturen in Nur-Lese-Kontexten, was wohl das Schlimmste aus beiden Welten ist.
Trotz der Probleme mit der Handhabung von selbstmutierenden Methoden in schreibgeschützten Kontexten bieten veränderbare Strukturen jedoch oft eine weitaus bessere Semantik als veränderbare Klassentypen. Betrachten Sie die folgenden drei Methodensignaturen:
struct PointyStruct {public int x,y,z;};
class PointyClass {public int x,y,z;};
void Method1(PointyStruct foo);
void Method2(ref PointyStruct foo);
void Method3(PointyClass foo);
Beantworten Sie für jede Methode die folgenden Fragen:
Antworten:
Frage 1:
Method1()
: nein (klare Absicht)
Method2()
: ja (klare Absicht)
Method3()
: ja (unklare Absicht)
Frage 2:
Method1()
: nein
Method2()
: nein (sofern nicht unsicher)
Method3()
: ja
Methode1 kann foo nicht ändern und erhält nie einen Verweis. Methode2 erhält einen kurzlebigen Verweis auf foo, mit dem sie die Felder von foo beliebig oft und in beliebiger Reihenfolge ändern kann, bis sie zurückkehrt, aber sie kann diesen Verweis nicht beibehalten. Bevor Methode2 zurückkehrt, sind, sofern sie keinen unsicheren Code verwendet, alle Kopien, die von ihrem 'foo'-Verweis gemacht wurden, verschwunden. Methode3 erhält im Gegensatz zu Methode2 einen beliebig teilbaren Verweis auf foo, und es ist nicht abzusehen, was sie damit tun wird. Vielleicht ändert sie foo überhaupt nicht, vielleicht ändert sie foo und kehrt dann zurück, oder sie gibt einen Verweis auf foo an einen anderen Thread weiter, der ihn zu einem beliebigen Zeitpunkt in der Zukunft auf irgendeine beliebige Weise verändern könnte. Die einzige Möglichkeit, das einzuschränken, was Method3 mit einem veränderlichen Klassenobjekt, das ihr übergeben wird, tun könnte, wäre, das veränderliche Objekt in einem schreibgeschützten Wrapper zu kapseln, was hässlich und umständlich ist.
Arrays von Strukturen bieten eine wunderbare Semantik. Bei einem RectArray[500] vom Typ Rectangle ist es klar und offensichtlich, wie man z.B. Element 123 nach Element 456 kopiert und dann einige Zeit später die Breite von Element 123 auf 555 setzt, ohne Element 456 zu stören. "RectArray[432] = RectArray[321]; ...; RectArray[123].Width = 555;". Wenn man weiß, dass Rectangle eine Struktur mit einem Ganzzahlfeld namens Width ist, weiß man alles, was man über die obigen Anweisungen wissen muss.
Nehmen wir nun an, RectClass sei eine Klasse mit denselben Feldern wie Rectangle und man wolle dieselben Operationen mit einem RectClassArray[500] vom Typ RectClass durchführen. Vielleicht soll das Array 500 vorinitialisierte unveränderliche Referenzen auf veränderbare RectClass-Objekte enthalten. In diesem Fall wäre der richtige Code so etwas wie "RectClassArray[321].SetBounds(RectClassArray[456]); ...; RectClassArray[321].X = 555;". Vielleicht wird davon ausgegangen, dass das Array Instanzen enthält, die sich nicht ändern werden, so dass der richtige Code eher wie folgt aussehen würde: "RectClassArray[321] = RectClassArray[456]; ...; RectClassArray[321] = New RectClass(RectClassArray[321]); RectClassArray[321]. X = 555;" Um zu wissen, was man tun soll, müsste man viel mehr über die RectClass wissen (z. B. ob sie einen Kopierkonstruktor, eine copy-from-Methode usw. unterstützt) und über die beabsichtigte Verwendung des Arrays. Das ist bei weitem nicht so sauber wie die Verwendung einer Struktur.
Um sicher zu sein, gibt es leider keine schöne Möglichkeit für eine andere Containerklasse als ein Array, um die saubere Semantik eines struct array zu bieten. Das Beste, was man tun könnte, wenn man wollte, dass eine Sammlung z.B. mit einer Zeichenkette indiziert wird, wäre wahrscheinlich, eine generische "ActOnItem"-Methode anzubieten, die eine Zeichenkette für den Index, einen generischen Parameter und einen Delegaten akzeptiert, der per Referenz sowohl den generischen Parameter als auch das Sammlungselement übergeben würde. Das würde fast die gleiche Semantik als Struktur-Arrays ermöglichen, aber es sei denn, die vb.net und C# Menschen können verfolgt werden, um eine schöne Syntax bieten, der Code wird klobig aussehende, auch wenn es vernünftig Leistung (Übergabe eines generischen Parameters würde für die Verwendung eines statischen Delegaten ermöglichen und würde jede Notwendigkeit, alle temporären Klasseninstanzen zu erstellen vermeiden).
Ich persönlich bin verärgert über den Hass, den Eric Lippert et al. in Bezug auf veränderbare Werttypen versprühen. Sie bieten eine viel sauberere Semantik als die vielseitigen Referenztypen, die überall verwendet werden. Trotz einiger Einschränkungen bei der Unterstützung von Werttypen in .net gibt es viele Fälle, in denen veränderbare Werttypen besser geeignet sind als jede andere Art von Entität.
CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.
25 Stimmen
Die Behauptung, veränderbare Strukturen seien böse, ist wie die Behauptung, veränderbare
int
s,bool
s, und alle anderen Werttypen sind böse. Es gibt Fälle für Veränderbarkeit und für Unveränderbarkeit. Diese Fälle hängen von der Rolle ab, die die Daten spielen, nicht von der Art der Speicherzuweisung/-freigabe.63 Stimmen
@slipp
int
ybool
sind pas wandelbar..2 Stimmen
.
-Syntax, wodurch Operationen mit ref-typed data und value-typed data gleich aussehen, obwohl sie sich deutlich unterscheiden. Dies ist ein Fehler von C#s Eigenschaften, nicht von Structs - einige Sprachen bieten eine alternativea[V][X] = 3.14
Syntax für In-Place-Mutationen. In C# ist es besser, Struktur-Member-Mutator-Methoden wie 'MutateV(Action<ref Vector2> mutator)` anzubieten und sie wie folgt zu verwendena.MutateV((v) => { v.X = 3; })
(Das Beispiel ist aufgrund der Einschränkungen, die C# hinsichtlich derref
Schlüsselwort, sollte aber mit einigen Umgehungen möglich sein) .0 Stimmen
@Sushi271 Umgehung (n): Eine Methode zur Überwindung eines Problems oder einer Einschränkung in einem Programm oder System. Dies ist eine Einschränkung in der Syntax von C#. Die ideale Verwendung der
ref
Schlüsselwort in einem generischen Typ ist nicht zweideutig; es wird nur nicht unterstützt. Das ist eine Einschränkung. Und ja, C# erzwingt in vielerlei Hinsicht eine unbequeme Syntax, um die Arbeit zu erledigen. Das klassischste Beispiel für die Unbeholfenheit von C# istpublic
immer und immer wieder für jedes Mitglied zu verwenden, anstatt eine C++-ähnlichepublic:
Präfix oder ein separates Ruby-artigespublic FieldName1, FieldName2, MethodName1, MethodName2;
0 Stimmen
@Sushi271 Strukturen mit vielen Mitgliedern sind wegen der Ineffizienz des Kopierens vieler Datenelemente nicht üblich. Die Anwendungsfälle sind die gleichen, aber gute Programmierer werden (und haben, historisch gesehen) einen anderen Ansatz wählen, um die Effizienz zu steigern. Also, ja, leichtgewichtig ist in der Regel besser für jeden Wert-typed Daten.
3 Stimmen
@Slipp Nun, ich denke genau das Gegenteil über diese Art von Strukturen. Warum denkst du, dass Strukturen, die bereits in der .NET Bibliothek implementiert sind, wie DateTime oder TimeSpan (also ähnliche) unveränderlich sind? Vielleicht könnte es nützlich sein, nur ein Mitglied solcher struct's var zu ändern, aber es ist einfach zu unbequem, führt zu zu vielen Problemen. Eigentlich sind Sie falsch über das, was tut der Prozessor berechnen, da C# nicht zu Assembler kompilieren, es kompiliert zu IL. In IL (vorausgesetzt, wir haben bereits die Variable namens
x
) umfasst dieser einzelne Vorgang 4 Anweisungen:ldloc.0
(lädt die Variable 0-Index in...1 Stimmen
... Typ.
T
Typ ist. Ref ist nur ein Schlüsselwort, das die Variable, die an eine Methode übergeben wird, zu einer Methode selbst macht, nicht zu einer Kopie davon. Es ist auch sinnvoll für die Referenztypen, da wir die die Variable d.h. der Verweis außerhalb der Methode wird auf ein anderes Objekt zeigen, nachdem er innerhalb der Methode geändert wurde. Daref T
ist kein Typ, aber die Art und Weise der Übergabe eines Methodenparameters kann man nicht in<>
denn dort können nur Typen abgelegt werden. Also ist es einfach falsch. Vielleicht wäre es praktisch, dies zu tun, vielleicht das C#-Team könnte dies für einige neue Version zu machen, aber im Moment sind sie auf einige arbeiten...1 Stimmen
... viel wichtigere Dinge wie Null-Bedingungsoperatoren oder Auto-Eigenschaftsinitialisierungen. Was die
public
... Ich mag eigentlich die C# Art, dies zu tun. Es ist tatsächlich einfacher zu refaktorisieren, weil das Löschen/Hinzufügen einer Public an einer Stelle für ein Mitglied sich nicht auf alle anderen Mitglieder auswirkt. In C++ muss ich oft zwei Labels hinzufügen (z.B. public: und private:), oder die Methode in einen anderen Teil der Klasse verschieben. Das ist lästig. Jedenfalls wurde mir durch die Erwähnung dieses Themas klar, dass wir diese Diskussion beenden sollten. Wir fangen an, uns über unsere eigenen Meinungen zu streiten, und wir könnten uns jahrelang streiten, ohne eine gemeinsame Meinung zu finden.1 Stimmen
Boden. Jeder hat das Recht auf seine eigene Meinung. Sie, Sir, scheinen mehr an C++ gebunden zu sein, während meine Hauptsprache in den letzten 8 Jahren C# war. Es ist sicherlich lehrreich, den Standpunkt eines anderen zu bestimmten Themen zu erfahren, aber im Moment sind wir weit vom Hauptthema abgewichen. Vielen Dank also für Ihren Beitrag.
1 Stimmen
Wir schreiben das Jahr 2017 und die veränderbaren Strukturen sind zurück! :D Wie immer geht es um das Wissen, wie und wann man sie verwendet. Das heißt, ich würde wahrscheinlich jedem Anfänger sagen "Halten Sie sich von structs fern oder machen Sie sie zumindest veränderbar - kommen Sie später darauf zurück, wenn Sie mehr Erfahrung haben, sie bieten besondere Vorteile" . (Zu verstehen, dass eine Referenz ein spezieller Adresswerttyp mit speziellen Operatorüberladungen ist, löst alles, aber das ist für Anfänger einfach zu viel zu kauen).
1 Stimmen
Ich stimme @AnorZaken zu, dass veränderbare Strukturen nur dann "böse" sind, wenn man den Unterschied zwischen Wert- und Referenztypen nicht kennt, ansonsten kann man Strukturen verwenden, auch wenn sie veränderbar sind, solange man weiß, was man tut. (btw .NET selbst hat viele veränderbare Strukturen)
0 Stimmen
Diese Frage hätte schon vor Jahren gesperrt werden sollen, da sie hauptsächlich auf Meinungen beruht. Ob Sie gültige Anwendungsfälle von veränderbaren Strukturen ignorieren wollen oder nicht, ist allein Ihre Entscheidung als Programmierer. Aber darüber zu debattieren, ob etwas, das es gibt, existieren sollte oder nicht, hat hier auf SO keinen Sinn, insbesondere wenn man bedenkt, dass es eine Fülle von Ressourcen gibt, die es jedem ermöglichen, diese Unterscheidung selbst zu treffen.