Strukturen mit öffentlichen veränderlichen Feldern oder Eigenschaften sind nicht böse.
Strukturmethoden (im Gegensatz zu Eigenschaftssetzern), die "dies" mutieren, sind etwas böse, nur weil .net keine Möglichkeit bietet, sie von Methoden zu unterscheiden, die dies nicht tun. Strukturmethoden, die "dies" nicht mutieren, sollten auch auf schreibgeschützten Strukturen aufgerufen werden können, ohne dass ein defensives Kopieren erforderlich ist. Methoden, die "dies" mutieren, sollten für schreibgeschützte Strukturen überhaupt nicht aufrufbar sein. Da .net nicht verbieten möchte, dass Strukturmethoden, die "this" nicht ändern, für schreibgeschützte Strukturen aufgerufen werden, aber nicht zulassen möchten, dass schreibgeschützte Strukturen mutiert werden, kopiert es defensiv Strukturen in schreibgeschützte Strukturen. nur Kontexte, wohl das Schlimmste aus beiden Welten.
Trotz der Probleme beim Umgang mit selbstmutierenden Methoden in schreibgeschützten Kontexten bieten veränderbare Strukturen häufig eine Semantik, die veränderlichen Klassentypen weit überlegen ist. Betrachten Sie die folgenden drei Methodensignaturen:
struct PointyStruct {public int x, y, z;};
Klasse 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:
- Angenommen, die Methode verwendet keinen "unsicheren" Code, könnte sie foo ändern?
- Wenn vor dem Aufruf der Methode keine externen Verweise auf 'foo' vorhanden sind, kann danach ein externer Verweis vorhanden sein?
Antworten:
Frage 1 ::
Method1()
nein (klare Absicht)
Method2()
: ja (klare Absicht)
Method3()
: ja (unsichere Absicht)
Frage 2
Method1()
:: nein
Method2()
: nein (sofern nicht unsicher)
Method3()
: ja
Methode1 kann foo nicht ändern und erhält nie eine Referenz. Methode2 erhält eine kurzlebige Referenz auf foo, mit der die Felder von foo beliebig oft in beliebiger Reihenfolge geändert werden können, bis sie zurückgegeben wird. Diese Referenz kann jedoch nicht beibehalten werden. Bevor Method2 zurückkehrt, sind alle Kopien, die möglicherweise von seiner 'foo'-Referenz erstellt wurden, verschwunden, sofern kein unsicherer Code verwendet wird. Methode3 erhält im Gegensatz zu Methode2 einen promisku teilbaren Verweis auf foo, und es ist nicht abzusehen, was es damit machen könnte. Es könnte foo überhaupt nicht ändern, es könnte foo ändern und dann zurückkehren, oder es könnte einen Verweis auf foo auf einen anderen Thread geben, der es zu einem beliebigen zukünftigen Zeitpunkt auf willkürliche Weise mutieren könnte.
Arrays von Strukturen bieten eine wunderbare Semantik. Bei RectArray [500] vom Typ Rectangle ist es klar und offensichtlich, wie man z. B. das Element 123 in das Element 456 kopiert und einige Zeit später die Breite des Elements 123 auf 555 setzt, ohne das Element 456 zu stören. "RectArray [432] = RectArray [321 ]; ...; RectArray [123] .Width = 555; ". Wenn Sie wissen, dass Rectangle eine Struktur mit einem ganzzahligen Feld namens Width ist, erfahren Sie alles, was Sie über die obigen Anweisungen wissen müssen.
Angenommen, RectClass war eine Klasse mit denselben Feldern wie Rectangle, und man wollte dieselben Operationen für ein RectClassArray [500] vom Typ RectClass ausführen. Möglicherweise soll das Array 500 vorinitialisierte unveränderliche Verweise auf veränderbare RectClass-Objekte enthalten. In diesem Fall wäre der richtige Code etwa "RectClassArray [321] .SetBounds (RectClassArray [456]); ...; RectClassArray [321] .X = 555;". Möglicherweise wird angenommen, dass das Array Instanzen enthält, die sich nicht ändern, sodass der richtige Code eher "RectClassArray [321] = RectClassArray [456]; ...; RectClassArray [321] = New RectClass (RectClassArray [321") lautet ]); RectClassArray [321] .X = 555; " Um zu wissen, was man tun soll, müsste man viel mehr über RectClass wissen (z. B. unterstützt es einen Kopierkonstruktor, eine Kopiermethode usw.). ) und die beabsichtigte Verwendung des Arrays. Nicht annähernd so sauber wie mit einer Struktur.
Natürlich gibt es für keine andere Containerklasse als ein Array eine gute Möglichkeit, die saubere Semantik eines Strukturarrays anzubieten. Das Beste, was man tun könnte, wenn man möchte, dass eine Sammlung mit z. B. einer Zeichenfolge indiziert wird, wäre wahrscheinlich, eine generische "ActOnItem" -Methode anzubieten, die eine Zeichenfolge für den Index, einen generischen Parameter und einen Delegaten akzeptiert, der übergeben wird durch Bezugnahme sowohl auf den generischen Parameter als auch auf das Sammlungselement. Das würde fast die gleiche Semantik wie Struktur-Arrays ermöglichen, aber wenn die Leute vb.net und C # nicht dazu gebracht werden können, eine nette Syntax anzubieten, wird der Code klobig aussehen, selbst wenn er eine vernünftige Leistung aufweist (das Übergeben eines generischen Parameters würde dies tun) die Verwendung eines statischen Delegaten zulassen und die Notwendigkeit vermeiden, temporäre Klasseninstanzen zu erstellen).
Persönlich ärgere ich mich über den Hass von Eric Lippert et al. Spucke in Bezug auf veränderbare Werttypen. Sie bieten eine viel sauberere Semantik als die promiskuitiven Referenztypen, die überall verwendet werden. Trotz einiger Einschränkungen bei der Unterstützung von .net für Werttypen gibt es viele Fälle, in denen veränderbare Werttypen besser passen als jede andere Art von Entität.
int
s,bool
s und alle anderen Werttypen seien böse. Es gibt Fälle für Veränderlichkeit und Unveränderlichkeit. Diese Fälle hängen von der Rolle der Daten ab, nicht von der Art der Speicherzuweisung / -freigabe.