Nach den Diskussionen hier auf SO habe ich bereits mehrmals die Bemerkung gelesen, dass veränderbare Strukturen „böse“ sind (wie in der Antwort auf diese Frage ).
Was ist das eigentliche Problem mit Veränderlichkeit und Strukturen in C #?
c#
struct
immutability
mutable
Dirk Vollmar
quelle
quelle
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.int
undbool
sind nicht veränderlich ...
-Ssyntax, sodass Vorgänge mit ref-typisierten Daten und werttypisierten Daten gleich aussehen, obwohl sie sich deutlich unterscheiden. Dies liegt an den Eigenschaften von C # und nicht an den Strukturen. Einige Sprachen bieten eine alternativea[V][X] = 3.14
Syntax für die direkte Mutation. In C # sollten Sie besser Mutator-Methoden für Strukturmitglieder wie 'MutateV (Action <ref Vector2> mutator)' anbieten und wiea.MutateV((v) => { v.X = 3; })
folgtref
verwenden (Beispiel ist aufgrund der Einschränkungen, die C # hinsichtlich des Schlüsselworts hat, zu stark vereinfacht , aber mit einigen Problemumgehungen sollten möglich sein) .x
) besteht diese einzelne Operation aus 4 Anweisungen:ldloc.0
(T
ist Typ. Ref ist nur ein Schlüsselwort, mit dem Variablen an eine Methode selbst übergeben werden, keine Kopie davon. Es hat auch Sinn für die Referenztypen, da wir die Variable ändern können , dh die Referenz außerhalb der Methode zeigt auf ein anderes Objekt, nachdem sie innerhalb der Methode geändert wurde. Daref T
es sich nicht um einen Typ handelt, sondern um die Übergabe eines Methodenparameters, können Sie ihn nicht einfügen<>
, da nur Typen dort abgelegt werden können. Es ist also einfach falsch. Vielleicht wäre es bequem, vielleicht könnte das C # -Team dies für eine neue Version machen, aber imAntworten:
Strukturen sind Werttypen, dh sie werden kopiert, wenn sie weitergegeben werden.
Wenn Sie also eine Kopie ändern, ändern Sie nur diese Kopie, nicht das Original und keine anderen Kopien, die möglicherweise vorhanden sind.
Wenn Ihre Struktur unveränderlich ist, sind alle automatischen Kopien, die sich aus der Übergabe des Werts ergeben, gleich.
Wenn Sie es ändern möchten, müssen Sie dies bewusst tun, indem Sie eine neue Instanz der Struktur mit den geänderten Daten erstellen. (keine Kopie)
quelle
Wo soll ich anfangen?
Eric Lipperts Blog ist immer gut für ein Zitat:
Erstens neigen Sie dazu, Änderungen leicht zu verlieren ... zum Beispiel, wenn Sie Dinge aus einer Liste streichen:
Was hat sich daran geändert? Nichts nützliches ...
Das gleiche gilt für Eigenschaften:
zwingt dich zu tun:
weniger kritisch gibt es ein Größenproblem; veränderbare Objekte sind in der Regel mehrere Eigenschaften aufweisen; Wenn Sie jedoch eine Struktur mit zwei
int
s, astring
, aDateTime
und a habenbool
, können Sie sehr schnell viel Speicher durchbrennen. Mit einer Klasse können mehrere Anrufer einen Verweis auf dieselbe Instanz gemeinsam nutzen (Verweise sind klein).quelle
++
Betreiber. In diesem Fall schreibt der Compiler nur die explizite Zuweisung selbst, anstatt den Programmierer zu belästigen.Ich würde nicht böse sagen, aber Veränderlichkeit ist oft ein Zeichen der Überanstrengung des Programmierers, ein Maximum an Funktionalität bereitzustellen. In der Realität wird dies häufig nicht benötigt, was wiederum die Schnittstelle kleiner, benutzerfreundlicher und schwerer falsch zu verwenden macht (= robuster).
Ein Beispiel hierfür sind Lese- / Schreib- und Schreib- / Schreibkonflikte unter Rennbedingungen. Diese können in unveränderlichen Strukturen einfach nicht auftreten, da ein Schreibvorgang keine gültige Operation ist.
Auch ich behaupten , dass Veränderlichkeit ist so gut wie nie wirklich gebraucht , der Programmierer nur denkt , dass es vielleicht in der Zukunft sein. Zum Beispiel ist es einfach nicht sinnvoll, ein Datum zu ändern. Erstellen Sie stattdessen ein neues Datum basierend auf dem alten. Dies ist eine billige Operation, daher spielt die Leistung keine Rolle.
quelle
float
Komponenten einer Grafiktransformation zurückgeben soll. Wenn eine solche Methode eine exponierte Feldstruktur mit sechs Komponenten zurückgibt, ist es offensichtlich, dass das Ändern der Felder der Struktur das Grafikobjekt, von dem sie empfangen wurde, nicht ändert. Wenn eine solche Methode ein veränderbares Klassenobjekt zurückgibt, ändert das Ändern der Eigenschaften möglicherweise das zugrunde liegende Grafikobjekt und möglicherweise auch nicht - niemand weiß es wirklich.Veränderliche Strukturen sind nicht böse.
Sie sind unter Hochleistungsbedingungen unbedingt erforderlich. Zum Beispiel, wenn Cache-Zeilen und / oder 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 erkennen, dass die Syntax von C # nicht dazu beiträgt, den Zugriff eines Mitglieds eines Werttyps oder eines Referenztyps zu unterscheiden, also bin ich alles für beiträgt, Werttyps Daher , unveränderliche Strukturen, die Unveränderlichkeit erzwingen, gegenüber veränderlichen Strukturen zu bevorzugen.
Anstatt unveränderliche Strukturen einfach als "böse" zu bezeichnen, würde ich empfehlen, die Sprache anzunehmen und eine hilfreichere und konstruktivere Faustregel zu befürworten.
Beispiel: "Strukturen sind Werttypen, die standardmäßig kopiert werden. Sie benötigen eine Referenz, wenn Sie sie nicht kopieren möchten" oder "Versuchen Sie zuerst, mit schreibgeschützten Strukturen zu arbeiten" .
quelle
struct
mit öffentlichen Feldern), als eine Klasse zu definieren, die ungeschickt verwendet werden kann, um die gleichen Ziele zu erreichen, oder um einer Struktur eine Menge Junk hinzuzufügen, damit sie eine solche Klasse emuliert (anstatt sie zu haben) benimm dich wie eine Reihe von Variablen, die mit Klebeband zusammengeklebt sind, was man eigentlich will)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:
Beantworten Sie für jede Methode die folgenden Fragen:
Antworten:
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.
quelle
Rectangle
könnte ich leicht eine gemeinsame Situation finden, in der Sie höchst unklares Verhalten bekommen. Beachten Sie, dass WinForms einen veränderlichenRectangle
Typ implementiert, der in derBounds
Eigenschaft des Formulars verwendet wird . Wenn ich Grenzen ändern möchte, möchte ich Ihre nette Syntax verwenden:form.Bounds.X = 10;
Dies ändert jedoch genau nichts am Formular (und erzeugt einen schönen Fehler, der Sie darüber informiert). Inkonsistenz ist der Fluch der Programmierung und deshalb ist Unveränderlichkeit erwünscht.Werttypen repräsentieren grundsätzlich unveränderliche Konzepte. Fx, es macht keinen Sinn, einen mathematischen Wert wie eine ganze Zahl, einen Vektor usw. zu haben und ihn dann ändern zu können. Das wäre wie eine Neudefinition der Bedeutung eines Wertes. Anstatt einen Wertetyp zu ändern, ist es sinnvoller, einen anderen eindeutigen Wert zuzuweisen. Denken Sie an die Tatsache, dass Werttypen verglichen werden, indem Sie alle Werte ihrer Eigenschaften vergleichen. Der Punkt ist, dass wenn die Eigenschaften gleich sind, es die gleiche universelle Darstellung dieses Wertes ist.
Wie Konrad erwähnt, ist es auch nicht sinnvoll, ein Datum zu ändern, da der Wert diesen eindeutigen Zeitpunkt darstellt und keine Instanz eines Zeitobjekts, das einen Status oder eine Kontextabhängigkeit aufweist.
Ich hoffe, das macht für Sie Sinn. Es geht sicher mehr um das Konzept, das Sie mit Werttypen erfassen möchten, als um praktische Details.
quelle
int
Iterator, der völlig unbrauchbar wäre, wenn er unveränderlich wäre. Ich denke, Sie kombinieren "Compiler- / Laufzeitimplementierungen von Werttypen" mit "Variablen, die in einen Werttyp eingegeben wurden" - letzteres ist sicherlich für jeden der möglichen Werte veränderbar.Es gibt einige andere Eckfälle, die aus Sicht des Programmierers zu unvorhersehbarem Verhalten führen können.
Unveränderliche Werttypen und schreibgeschützte Felder
Veränderbare Werttypen und Arrays
Angenommen, wir haben ein Array unserer
Mutable
Struktur und rufen dieIncrementI
Methode für das erste Element dieses Arrays auf. Welches Verhalten erwarten Sie von diesem Anruf? Sollte es den Wert des Arrays ändern oder nur eine Kopie?Veränderliche Strukturen sind also nicht böse, solange Sie und der Rest des Teams klar verstehen, was Sie tun. Es gibt jedoch zu viele Eckfälle, in denen sich das Programmverhalten von den erwarteten unterscheidet, was zu subtilen, schwer zu erzeugenden und schwer zu verstehenden Fehlern führen kann.
quelle
T[]
und einen ganzzahligen Index enthält, und vorausgesetzt, dass ein Zugriff auf eine Eigenschaft vom TypArrayRef<T>
als interpretiert wird Zugriff auf das entsprechende Array-Element) [Wenn eine Klasse eineArrayRef<T>
für einen anderen Zweck verfügbar machen möchte , kann sie - im Gegensatz zu einer Eigenschaft - eine Methode zum Abrufen bereitstellen]. Leider gibt es keine derartigen Bestimmungen.Wenn Sie jemals in einer Sprache wie C / C ++ programmiert haben, können Strukturen als veränderbar verwendet werden. Gib sie einfach mit ref weiter und es gibt nichts, was schief gehen kann. Das einzige Problem, das ich finde, sind die Einschränkungen des C # -Compilers, und in einigen Fällen kann ich das dumme Ding nicht zwingen, anstelle einer Kopie einen Verweis auf die Struktur zu verwenden (wie wenn eine Struktur Teil einer C # -Klasse ist ).
So wandelbar structs nicht böse sind, hat C # gemacht sie böse. Ich verwende ständig veränderbare Strukturen in C ++ und sie sind sehr praktisch und intuitiv. Im Gegensatz dazu hat C # mich dazu gebracht, Strukturen als Mitglieder von Klassen vollständig aufzugeben, da sie mit Objekten umgehen. Ihre Bequemlichkeit hat uns unsere gekostet.
quelle
readonly
, aber wenn man dies vermeidet, sind Klassenfelder von Strukturtypen in Ordnung. Die einzige wirklich grundlegende Einschränkung von Strukturen besteht darin, dass ein Strukturfeld eines veränderlichen Klassentyps wieint[]
Identität oder einen unveränderlichen Satz von Werten einkapseln kann, jedoch nicht zum Einkapseln veränderlicher Werte verwendet werden kann, ohne auch eine unerwünschte Identität einzukapseln.Wenn Sie sich an die Strukturen halten, für die sie bestimmt sind (in C #, Visual Basic 6, Pascal / Delphi, C ++ - Strukturtyp (oder Klassen), wenn sie nicht als Zeiger verwendet werden), werden Sie feststellen, dass eine Struktur nicht mehr als eine zusammengesetzte Variable ist . Dies bedeutet: Sie behandeln sie als einen gepackten Satz von Variablen unter einem gemeinsamen Namen (eine Datensatzvariable, aus der Sie Mitglieder referenzieren).
Ich weiß, dass dies viele Menschen verwirren würde, die tief an OOP gewöhnt sind, aber das ist nicht genug Grund zu sagen, dass solche Dinge von Natur aus böse sind, wenn sie richtig verwendet werden. Einige Strukturen sind unveränderlich, wie sie beabsichtigen (dies ist der Fall bei Python
namedtuple
), aber es ist ein anderes zu berücksichtigendes Paradigma.Ja: Strukturen erfordern viel Speicher, aber es wird nicht genau mehr Speicher sein, wenn Sie Folgendes tun:
verglichen mit:
Der Speicherverbrauch ist im unveränderlichen Fall mindestens gleich oder sogar noch höher (obwohl dieser Fall für den aktuellen Stapel je nach Sprache nur vorübergehend ist).
Aber schließlich sind Strukturen Strukturen , keine Objekte. In POO ist die Haupteigenschaft eines Objekts seine Identität , die meistens nicht mehr als seine Speicheradresse ist. Struct steht für Datenstruktur (kein richtiges Objekt, daher haben sie sowieso keine Identität), und Daten können geändert werden. In anderen Sprachen ist record (anstelle von struct , wie dies bei Pascal der Fall ist) das Wort und hat denselben Zweck: Nur eine Datensatzvariable, die aus Dateien gelesen, geändert und in Dateien gespeichert werden soll (das ist die Hauptvariable) Verwenden Sie und in vielen Sprachen können Sie sogar die Datenausrichtung im Datensatz definieren, während dies bei ordnungsgemäß aufgerufenen Objekten nicht unbedingt der Fall ist.
Willst du ein gutes Beispiel? Strukturen werden verwendet, um Dateien leicht zu lesen. Python hat diese Bibliothek , da sie objektorientiert ist und keine Unterstützung für Strukturen bietet, musste sie auf eine andere Weise implementiert werden, was etwas hässlich ist. In Sprachen, die Strukturen implementieren, ist diese Funktion ... integriert. Versuchen Sie, einen Bitmap-Header mit einer geeigneten Struktur in Sprachen wie Pascal oder C zu lesen. Dies ist einfach (wenn die Struktur ordnungsgemäß erstellt und ausgerichtet ist; in Pascal würden Sie keinen auf Datensätzen basierenden Zugriff verwenden, sondern Funktionen zum Lesen beliebiger Binärdaten). Für Dateien und direkten (lokalen) Speicherzugriff sind Strukturen also besser als Objekte. Bis heute sind wir an JSON und XML gewöhnt und vergessen daher die Verwendung von Binärdateien (und als Nebeneffekt die Verwendung von Strukturen). Aber ja: Sie existieren und haben einen Zweck.
Sie sind nicht böse. Verwenden Sie sie einfach für den richtigen Zweck.
Wenn Sie in Bezug auf Hämmer denken, werden Sie Schrauben als Nägel behandeln wollen, um herauszufinden, dass Schrauben schwerer in die Wand zu stecken sind, und es wird die Schuld der Schrauben sein, und sie werden die bösen sein.
quelle
Stellen Sie sich vor, Sie haben ein Array von 1.000.000 Strukturen. Jede Struktur, die eine Aktie mit Bid_price, Offer_price (möglicherweise Dezimalstellen) usw. darstellt, wird von C # / VB erstellt.
Stellen Sie sich vor, das Array wird in einem Speicherblock erstellt, der im nicht verwalteten Heap zugewiesen ist, damit ein anderer nativer Codethread gleichzeitig auf das Array zugreifen kann (möglicherweise ein hochleistungsfähiger Code, der Mathematik ausführt).
Stellen Sie sich vor, der C # / VB-Code lauscht einem Marktfeed von Preisänderungen. Dieser Code muss möglicherweise auf ein Element des Arrays zugreifen (für welche Sicherheit auch immer) und dann einige Preisfelder ändern.
Stellen Sie sich vor, dies geschieht zehntausend oder sogar hunderttausend Mal pro Sekunde.
Lassen Sie uns den Tatsachen ins Auge sehen. In diesem Fall möchten wir wirklich, dass diese Strukturen veränderlich sind. Sie müssen es sein, weil sie von einem anderen nativen Code gemeinsam genutzt werden, sodass das Erstellen von Kopien nicht hilfreich ist. Sie müssen es sein, weil das Erstellen einer Kopie von etwa 120-Byte-Strukturen mit diesen Raten Wahnsinn ist, insbesondere wenn ein Update tatsächlich nur ein oder zwei Bytes betrifft.
Hugo
quelle
Wenn etwas mutiert werden kann, gewinnt es ein Gefühl der Identität.
Da
Person
es veränderlich ist, ist es natürlicher, daran zu denken , Erics Position zu ändern, als Eric zu klonen, den Klon zu bewegen und das Original zu zerstören . Beide Operationen würden es schaffen, den Inhalt von zu änderneric.position
, aber eine ist intuitiver als die andere. Ebenso ist es intuitiver, Eric (als Referenz) für Methoden zur Modifizierung weiterzugeben. Es wird fast immer überraschend sein, einer Methode einen Klon von Eric zu geben. Wer mutieren will,Person
muss daran denken, nach einem Hinweis zu fragen,Person
sonst machen sie das Falsche.Wenn Sie den Typ unveränderlich machen, verschwindet das Problem. Wenn ich nicht ändern kann
eric
, macht es für mich keinen Unterschied, ob icheric
einen Klon von erhalte oder nichteric
. Im Allgemeinen kann ein Typ sicher als Wert übergeben werden, wenn sein gesamter beobachtbarer Status in Mitgliedern gespeichert ist, die entweder:Wenn diese Bedingungen erfüllt sind, verhält sich ein veränderbarer Werttyp wie ein Referenztyp, da der Empfänger bei einer flachen Kopie weiterhin die Originaldaten ändern kann.
Die Intuitivität eines Unveränderlichen
Person
hängt jedoch davon ab, was Sie versuchen. Wenn es sichPerson
nur um einen Datensatz über eine Person handelt, ist daran nichts Unintuitives.Person
Variablen repräsentieren wirklich abstrakte Werte , keine Objekte. (In diesem Fall wäre es wahrscheinlich angemessener, es umzubenennenPersonData
.) Wenn SiePerson
tatsächlich eine Person selbst modellieren, ist die Idee, ständig Klone zu erstellen und zu verschieben, albern, selbst wenn Sie die Gefahr vermieden haben, zu denken, dass Sie Änderungen vornehmen das Original. In diesem Fall wäre es wahrscheinlich natürlicher, einfach zu machenPerson
einen Referenztyp (dh eine Klasse) zu .Zugegeben, wie uns die funktionale Programmierung gelehrt hat, hat es Vorteile, alles unveränderlich zu machen (niemand kann sich heimlich an einen Verweis auf
eric
ihn halten und ihn mutieren), aber da dies in OOP nicht idiomatisch ist, wird es für jeden anderen, der mit Ihnen arbeitet, immer noch nicht intuitiv sein Code.quelle
foo
der einzige Verweis auf sein Ziel irgendwo im Universum vorhanden ist und nichts den Identitäts-Hash-Wert dieses Objekts erfasst hat, entspricht das Mutationsfeldfoo.X
semantisch dem Verweisen auffoo
ein neues Objekt, das genau dem entspricht, auf das es zuvor Bezug genommen hat, jedoch mitX
Halten Sie den gewünschten Wert. Bei Klassentypen ist es im Allgemeinen schwer zu wissen, ob mehrere Verweise auf etwas existieren, aber bei Strukturen ist es einfach: Sie tun es nicht.Thing
ein veränderlicher Klasse - Typ ist, eineThing[]
wird Objektidentitäten kapseln - ob man es will oder nicht - es sei denn , man , dass nicht sicherstellen kann ,Thing
in der Anordnung , auf die alle außerhalb Referenzen existieren jemals mutiert werden. Wenn die Array-Elemente keine Identität enthalten sollen, muss im Allgemeinen sichergestellt werden, dass entweder keine Elemente, auf die sie verweisen, mutiert werden oder dass keine externen Verweise auf Elemente vorhanden sind, die sie enthalten [hybride Ansätze können ebenfalls funktionieren ]. Keiner der Ansätze ist schrecklich bequem. WennThing
es sich um eine Struktur handelt,Thing[]
kapselt a nur Werte.Es hat nichts mit Strukturen zu tun (und auch nicht mit C #), aber in Java können Probleme mit veränderlichen Objekten auftreten, wenn es sich beispielsweise um Schlüssel in einer Hash-Map handelt. Wenn Sie sie nach dem Hinzufügen zu einer Karte ändern und der Hash-Code geändert wird , können böse Dinge passieren.
quelle
Wenn ich mir Code anschaue, sieht das für mich persönlich ziemlich klobig aus:
data.value.set (data.value.get () + 1);
eher als einfach
data.value ++; oder data.value = data.value + 1;
Die Datenkapselung ist nützlich, wenn Sie eine Klasse weitergeben und sicherstellen möchten, dass der Wert kontrolliert geändert wird. Wenn Sie jedoch öffentlich festgelegt sind und Funktionen erhalten, die kaum mehr tun, als den Wert auf das zu setzen, was jemals übergeben wird, wie ist dies eine Verbesserung gegenüber der einfachen Weitergabe einer öffentlichen Datenstruktur?
Wenn ich eine private Struktur innerhalb einer Klasse erstelle, habe ich diese Struktur erstellt, um eine Reihe von Variablen in einer Gruppe zu organisieren. Ich möchte in der Lage sein, diese Struktur innerhalb des Klassenbereichs zu ändern, keine Kopien dieser Struktur abzurufen und neue Instanzen zu erstellen.
Für mich verhindert dies eine gültige Verwendung von Strukturen, die zum Organisieren öffentlicher Variablen verwendet werden. Wenn ich Zugriffssteuerung wünschte, würde ich eine Klasse verwenden.
quelle
Es gibt mehrere Probleme mit dem Beispiel von Eric Lippert. Es soll den Punkt veranschaulichen, an dem Strukturen kopiert werden, und wie dies ein Problem sein kann, wenn Sie nicht vorsichtig sind. Wenn ich mir das Beispiel anschaue, sehe ich es als Ergebnis einer schlechten Programmiergewohnheit und nicht wirklich als Problem mit der Struktur oder der Klasse.
Eine Struktur soll nur öffentliche Mitglieder haben und keine Kapselung erfordern. Wenn ja, sollte es wirklich ein Typ / eine Klasse sein. Sie brauchen wirklich nicht zwei Konstrukte, um dasselbe zu sagen.
Wenn Sie eine Klasse haben, die eine Struktur einschließt, würden Sie eine Methode in der Klasse aufrufen, um die Mitgliedsstruktur zu mutieren. Dies ist, was ich als gute Programmiergewohnheit tun würde.
Eine ordnungsgemäße Implementierung wäre wie folgt.
Es sieht so aus, als wäre es ein Problem mit der Programmiergewohnheit im Gegensatz zu einem Problem mit der Struktur selbst. Strukturen sollen veränderlich sein, das ist die Idee und Absicht.
Das Ergebnis der Änderungen voila verhält sich wie erwartet:
1 2 3 Drücken Sie eine beliebige Taste, um fortzufahren. . .
quelle
Veränderbare Daten haben viele Vor- und Nachteile. Der Millionen-Dollar-Nachteil ist Aliasing. Wenn derselbe Wert an mehreren Stellen verwendet wird und einer von ihnen ihn ändert, scheint er sich auf magische Weise an den anderen Stellen geändert zu haben, an denen er verwendet wird. Dies hängt mit den Rennbedingungen zusammen, ist jedoch nicht mit diesen identisch.
Der Millionen-Dollar-Vorteil ist manchmal die Modularität. Mit dem veränderlichen Status können Sie sich ändernde Informationen vor Code verbergen, der nichts darüber wissen muss.
Die Kunst des Dolmetschers geht detailliert auf diese Kompromisse ein und gibt einige Beispiele.
quelle
Ich glaube nicht, dass sie böse sind, wenn sie richtig verwendet werden. Ich würde es nicht in meinen Produktionscode aufnehmen, aber ich würde es für so etwas wie strukturierte Unit-Testing-Mocks tun, bei denen die Lebensdauer einer Struktur relativ gering ist.
Mit dem Eric-Beispiel möchten Sie vielleicht eine zweite Instanz dieses Eric erstellen, aber Anpassungen vornehmen, da dies die Art Ihres Tests ist (dh Duplizieren und dann Ändern). Es spielt keine Rolle, was mit der ersten Instanz von Eric passiert, wenn wir Eric2 nur für den Rest des Testskripts verwenden, es sei denn, Sie planen, ihn als Testvergleich zu verwenden.
Dies ist vor allem nützlich, um Legacy-Code zu testen oder zu ändern, der ein bestimmtes Objekt (den Punkt von Strukturen) flach definiert. Durch eine unveränderliche Struktur wird jedoch dessen Verwendung nicht störend.
quelle
Range<T>
Typ mit ElementenMinimum
undMaximum
Feldern vom TypT
und Code angegeben wirdRange<double> myRange = foo.getRange();
, sollten alle Garantien darüber, wasMinimum
und wasMaximum
enthalten, stammenfoo.GetRange();
. NachdemRange
wäre eine belichteter-Feld - Struktur deutlich machen , dass es wird kein Verhalten der eigenen hinzufügen.