Warum sind veränderbare Strukturen „böse“?


485

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 #?


12
Die Behauptung, veränderliche Strukturen seien böse, ist wie die Behauptung, veränderliche ints, bools 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.
Slipp D. Thompson

41
@ Slipp intund boolsind nicht veränderlich ..
Blorgbeard ist am

2
.-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 alternative a[V][X] = 3.14Syntax für die direkte Mutation. In C # sollten Sie besser Mutator-Methoden für Strukturmitglieder wie 'MutateV (Action <ref Vector2> mutator)' anbieten und wie a.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) .
Slipp D. Thompson

2
@ Slipp Nun, ich denke genau das Gegenteil über diese Art von Strukturen. Warum sind Ihrer Meinung nach Strukturen, die bereits in der .NET-Bibliothek implementiert sind, wie DateTime oder TimeSpan (also ähnliche), unveränderlich? Vielleicht könnte es nützlich sein, nur ein Mitglied der var einer solchen Struktur zu ändern, aber es ist einfach zu unpraktisch, was zu zu vielen Problemen führt. Tatsächlich liegen Sie falsch, was der Prozessor berechnet, da C # nicht in Assembler kompiliert wird, sondern in IL. In IL (vorausgesetzt, wir haben bereits die benannte Variable x) besteht diese einzelne Operation aus 4 Anweisungen: ldloc.0(
Lädt

1
... Art. Tist 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. Da ref Tes 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 im
Moment

Antworten:


290

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)


82
"Wenn Ihre Struktur unveränderlich ist, sind alle Kopien gleich." Nein, es bedeutet, dass Sie bewusst eine Kopie erstellen müssen, wenn Sie einen anderen Wert wünschen. Dies bedeutet, dass Sie beim Ändern einer Kopie nicht erwischt werden, wenn Sie glauben, das Original zu ändern.
Lucas

19
@Lucas Ich denke, Sie sprechen von einer anderen Art von Kopie. Ich spreche von den automatischen Kopien, die als Ergebnis der Wertübergabe erstellt wurden. Ihre "bewusst erstellte Kopie" unterscheidet sich absichtlich, weil Sie sie nicht versehentlich erstellt haben Nicht wirklich eine Kopie, sondern absichtlich neue Momente, die unterschiedliche Daten enthalten.
Trampster

3
Ihre Bearbeitung (16 Monate später) macht das etwas klarer. Ich stehe immer noch zu "(unveränderliche Struktur) bedeutet, dass Sie nicht beim Ändern einer Kopie erwischt werden, wenn Sie denken, dass Sie das Original ändern".
Lucas

6
@Lucas: Die Gefahr, eine Kopie einer Struktur zu erstellen, sie zu modifizieren und irgendwie zu denken, dass man das Original modifiziert (wenn die Tatsache, dass man ein Strukturfeld schreibt , die Tatsache offensichtlich macht , dass man nur seine Kopie schreibt), scheint Im Vergleich zu der Gefahr, dass jemand, der ein Klassenobjekt als Mittel zum Speichern der darin enthaltenen Informationen besitzt, das Objekt mutiert, um seine eigenen Informationen zu aktualisieren, und dabei die von einem anderen Objekt gehaltenen Informationen beschädigt.
Supercat

2
Der 3. Absatz klingt bestenfalls falsch oder unklar. Wenn Ihre Struktur unveränderlich ist, können Sie ihre Felder oder die Felder der erstellten Kopien einfach nicht ändern. „Wenn Sie es ändern wollen , müssen Sie ...“ , die zu irreführend ist, kann man nicht ändern , es immer , weder bewusst noch unbewusst. Das Erstellen einer neuen Instanz, für die die gewünschten Daten nichts mit der Originalkopie zu tun haben, außer mit derselben Datenstruktur.
Saeb Amini

167

Wo soll ich anfangen?

Eric Lipperts Blog ist immer gut für ein Zitat:

Dies ist ein weiterer Grund, warum veränderbare Werttypen böse sind. Versuchen Sie immer, Werttypen unveränderlich zu machen.

Erstens neigen Sie dazu, Änderungen leicht zu verlieren ... zum Beispiel, wenn Sie Dinge aus einer Liste streichen:

Foo foo = list[0];
foo.Name = "abc";

Was hat sich daran geändert? Nichts nützliches ...

Das gleiche gilt für Eigenschaften:

myObj.SomeProperty.Size = 22; // the compiler spots this one

zwingt dich zu tun:

Bar bar = myObj.SomeProperty;
bar.Size = 22;
myObj.SomeProperty = bar;

weniger kritisch gibt es ein Größenproblem; veränderbare Objekte sind in der Regel mehrere Eigenschaften aufweisen; Wenn Sie jedoch eine Struktur mit zwei ints, a string, a DateTimeund a haben bool, 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).


5
Na ja, aber der Compiler ist einfach so dumm. Nicht erlaubt die Zuordnung zu eigenschafts struct Mitglieder war IMHO eine dumme Design - Entscheidung, denn es ist für erlaubt ++Betreiber. In diesem Fall schreibt der Compiler nur die explizite Zuweisung selbst, anstatt den Programmierer zu belästigen.
Konrad Rudolph

12
@Konrad: myObj.SomeProperty.Size = 22 würde eine KOPIE von myObj.SomeProperty ändern. Der Compiler bewahrt Sie vor einem offensichtlichen Fehler. Und es ist NICHT für ++ erlaubt.
Lucas

@Lucas: Nun, der Mono C # -Compiler erlaubt es auf jeden Fall - da ich kein Windows habe, kann ich nicht nach dem Compiler von Microsoft suchen.
Konrad Rudolph

6
@Konrad - mit einer Indirektion weniger sollte es funktionieren; es ist das "Mutieren eines Wertes von etwas, das nur als vorübergehender Wert auf dem Stapel existiert und das im Begriff ist, ins Nichts zu verdampfen", was blockiert ist.
Marc Gravell

2
@Marc Gravell: Im vorherigen Code erhalten Sie ein "Foo", dessen Name "abc" ist und dessen andere Attribute die von List [0] sind, ohne List [0] zu stören. Wenn Foo eine Klasse wäre, müsste sie geklont und dann die Kopie geändert werden. Meiner Meinung nach ist das große Problem bei der Unterscheidung zwischen Werttyp und Klasse die Verwendung des "." Betreiber für zwei Zwecke. Wenn ich meine Druthers hätte, könnten die Klassen beide unterstützen "." und "->" für Methoden und Eigenschaften, aber die normale Semantik für "." Eigenschaften wären, eine neue Instanz mit dem entsprechenden geänderten Feld zu erstellen.
Supercat

71

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.


1
Eric Lippert sagt, sie sind ... siehe meine Antwort.
Marc Gravell

42
So sehr ich Eric Lippert respektiere, er ist nicht Gott (oder zumindest noch nicht). Der Blog-Beitrag, auf den Sie verlinken, und Ihr Beitrag oben sind natürlich vernünftige Argumente dafür, Strukturen unveränderlich zu machen, aber sie sind tatsächlich sehr schwach als Argumente dafür, niemals veränderbare Strukturen zu verwenden. Dieser Beitrag ist jedoch eine +1.
Stephen Martin

2
Bei der Entwicklung in C # benötigen Sie normalerweise ab und zu Veränderbarkeit - insbesondere bei Ihrem Geschäftsmodell, bei dem Streaming usw. reibungslos mit vorhandenen Lösungen funktionieren soll. Ich schrieb einen Artikel darüber, wie man mit veränderlichen UND unveränderlichen Daten arbeitet und die meisten Probleme im Zusammenhang mit Veränderlichkeit löst (ich hoffe): rickyhelgesson.wordpress.com/2012/07/17/…
Ricky Helgesson

3
@StephenMartin: Strukturen, die einen einzelnen Wert einkapseln, sollten häufig unveränderlich sein, aber Strukturen sind bei weitem das beste Medium zum Einkapseln fester Mengen unabhängiger, aber verwandter Variablen (wie die X- und Y-Koordinaten eines Punkts), die keine "Identität" als Gruppe. Strukturen, die zu diesem Zweck verwendet werden, sollten ihre Variablen im Allgemeinen als öffentliche Felder verfügbar machen . Ich würde die Vorstellung, dass es angemessener ist, eine Klasse als eine Struktur für solche Zwecke zu verwenden, als einfach falsch betrachten. Unveränderliche Klassen sind oft weniger effizient und veränderbare Klassen haben oft eine schreckliche Semantik.
Supercat

2
@StephenMartin: Betrachten Sie beispielsweise eine Methode oder Eigenschaft, die die sechs floatKomponenten 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.
Supercat

48

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


8
Ich würde auch davon ausgehen, dass es viel sinnvoller ist, den Compiler zu bitten, einen festen Satz von Variablen zu befestigen, wenn man einen festen Satz von Variablen zusammen mit Klebeband befestigen möchte, damit ihre Werte entweder separat oder als Einheit verarbeitet oder gespeichert werden können Variablen zusammen (dh deklarieren Sie a structmit ö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)
Supercat

41

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:

  1. Angenommen, die Methode verwendet keinen "unsicheren" Code, könnte sie foo ändern?
  2. 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.


1
@ Ron Warholic: Es ist nicht selbstverständlich, dass SomeRect ein Rechteck ist. Es könnte sich um einen anderen Typ handeln, der implizit von Rectangle typisiert werden kann. Der einzige systemdefinierte Typ, der implizit aus Rectangle typisiert werden kann, ist RectangleF, und der Compiler würde kreischen, wenn man versuchen würde, die Felder eines RectangleF an den Konstruktor von Rectangle zu übergeben (da die ersteren Single und die letzteren Integer sind). Es könnte benutzerdefinierte Strukturen geben, die solche impliziten Typecasts ermöglichen. Übrigens würde die erste Aussage genauso gut funktionieren, ob SomeRect ein Rectangle oder ein RectangleF ist.
Supercat

Alles, was Sie gezeigt haben, ist, dass Sie in einem erfundenen Beispiel glauben, dass eine Methode klarer ist. Wenn wir Ihr Beispiel mitnehmen, Rectanglekönnte ich leicht eine gemeinsame Situation finden, in der Sie höchst unklares Verhalten bekommen. Beachten Sie, dass WinForms einen veränderlichen RectangleTyp implementiert, der in der BoundsEigenschaft 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.
Ron Warholic

3
@Ron Warholic: BTW würde, I mag der Lage sein , zu sagen : "form.Bounds.X = 10;" und lassen Sie es einfach funktionieren, aber das System bietet keine saubere Möglichkeit, dies zu tun. Eine Konvention zum Offenlegen von Eigenschaften vom Werttyp als Methoden, die Rückrufe akzeptieren, könnte viel saubereren, effizienteren und bestätigungsrichtigeren Code bieten als jeder Ansatz, der Klassen verwendet.
Supercat

4
Diese Antwort ist so viel aufschlussreicher als einige der am besten bewerteten Antworten. Es ist irgendwie absurd, dass das Argument gegen veränderbare Werttypen auf der Vorstellung beruht, dass "das, was Sie erwarten" passiert, wenn Sie Aliasing und Mutation mischen. Das ist sowieso eine schreckliche Sache !
Eamon Nerbonne

2
@supercat: Wer weiß, vielleicht deckt die Ref-Return-Funktion, über die sie für C # 7 sprechen, diese Basis ab (ich habe sie mir nicht im Detail angesehen, aber sie klingt oberflächlich ähnlich).
Eamon Nerbonne

24

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.


Nun, sie sollten zumindest unveränderliche Konzepte darstellen ;-p
Marc Gravell

3
Nun, ich nehme an, sie hätten System.Drawing.Point unveränderlich machen können, aber es wäre meiner Meinung nach ein schwerwiegender Designfehler gewesen. Ich denke, Punkte sind eigentlich ein archetypischer Werttyp und sie sind veränderlich. Und sie verursachen für niemanden Probleme, außer für Anfänger, die wirklich früh programmieren.
Stephen Martin

3
Grundsätzlich denke ich, dass Punkte auch unveränderlich sein sollten, aber wenn es die Verwendung des Typs schwieriger oder weniger elegant macht, muss dies natürlich auch berücksichtigt werden. Es macht keinen Sinn, Codekonstrukte zu haben, die die besten Prinzipien unterstützen, wenn niemand sie verwenden möchte;)
Morten Christiansen

3
Werttypen sind nützlich, um einfache unveränderliche Konzepte darzustellen, aber exponierte Feldstrukturen sind die besten Typen, um kleine feste Mengen verwandter, aber unabhängiger Werte (wie die Koordinaten eines Punktes) zu halten oder weiterzugeben. Ein Speicherort eines solchen Werttyps kapselt die Werte seiner Felder und sonst nichts. Im Gegensatz dazu kann ein Speicherort eines veränderlichen Referenztyps verwendet werden, um den Status des veränderlichen Objekts zu speichern, kapselt aber auch die Identität aller anderen Referenzen im gesamten Universum, die für dasselbe Objekt existieren.
Supercat

4
„Werttypen stehen grundsätzlich für unveränderliche Konzepte“. Nein, das tun sie nicht. Eine der ältesten und nützlichsten Anwendungen einer werttypisierten Variablen ist ein intIterator, 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.
Slipp D. Thompson

19

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

    // Simple mutable structure. 
    // Method IncrementI mutates current state.
    struct Mutable
    {
        public Mutable(int i) : this() 
        {
            I = i;
        }

        public void IncrementI() { I++; }

        public int I { get; private set; }
    }

    // Simple class that contains Mutable structure
    // as readonly field
    class SomeClass 
    {
        public readonly Mutable mutable = new Mutable(5);
    }

    // Simple class that contains Mutable structure
    // as ordinary (non-readonly) field
    class AnotherClass 
    {
        public Mutable mutable = new Mutable(5);
    }

    class Program
    {
        void Main()
        {
            // Case 1. Mutable readonly field
            var someClass = new SomeClass();
            someClass.mutable.IncrementI();
            // still 5, not 6, because SomeClass.mutable field is readonly
            // and compiler creates temporary copy every time when you trying to
            // access this field
            Console.WriteLine(someClass.mutable.I);

            // Case 2. Mutable ordinary field
            var anotherClass = new AnotherClass();
            anotherClass.mutable.IncrementI();

            // Prints 6, because AnotherClass.mutable field is not readonly
            Console.WriteLine(anotherClass.mutable.I);
        }
    }

Veränderbare Werttypen und Arrays

Angenommen, wir haben ein Array unserer MutableStruktur und rufen die IncrementIMethode 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?

    Mutable[] arrayOfMutables = new Mutable[1];
    arrayOfMutables[0] = new Mutable(5);

    // Now we actually accessing reference to the first element
    // without making any additional copy
    arrayOfMutables[0].IncrementI();

    // Prints 6!!
    Console.WriteLine(arrayOfMutables[0].I);

    // Every array implements IList<T> interface
    IList<Mutable> listOfMutables = arrayOfMutables;

    // But accessing values through this interface lead
    // to different behavior: IList indexer returns a copy
    // instead of an managed reference
    listOfMutables[0].IncrementI(); // Should change I to 7

    // Nope! we still have 6, because previous line of code
    // mutate a copy instead of a list value
    Console.WriteLine(listOfMutables[0].I);

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.


5
Was passieren sollte, wenn .net-Sprachen eine etwas bessere Unterstützung für Werttypen hätten, wäre, dass Strukturmethoden verboten werden sollten, "dies" zu mutieren, es sei denn, sie werden ausdrücklich als solche deklariert, und Methoden, die so deklariert sind, sollten schreibgeschützt verboten werden Kontexte. Arrays veränderlicher Strukturen bieten nützliche Semantik, die mit anderen Mitteln nicht effizient erreicht werden kann.
Supercat

2
Dies sind gute Beispiele für sehr subtile Probleme, die sich aus veränderlichen Strukturen ergeben würden. Ich hätte nichts von diesem Verhalten erwartet. Warum gibt Ihnen ein Array eine Referenz, aber eine Schnittstelle gibt Ihnen einen Wert? Abgesehen von den ständigen Werten (was ich wirklich erwarten würde) hätte ich gedacht, dass es zumindest umgekehrt sein würde: Schnittstelle mit Referenzen; Arrays geben Werte ...
Dave Cousineau

@Sahuagin: Leider gibt es keinen Standardmechanismus, mit dem eine Schnittstelle eine Referenz verfügbar machen kann. Es gibt Möglichkeiten, wie .net solche Dinge sicher und nützlich ausführen kann (z. B. indem eine spezielle "ArrayRef <T>" - Struktur definiert wird, die einen T[]und einen ganzzahligen Index enthält, und vorausgesetzt, dass ein Zugriff auf eine Eigenschaft vom Typ ArrayRef<T>als interpretiert wird Zugriff auf das entsprechende Array-Element) [Wenn eine Klasse eine ArrayRef<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.
Supercat

2
Oh mein Gott ... das macht veränderbare Strukturen verdammt böse!
Nawfal

1
Ich mag diese Antwort, weil sie sehr wertvolle Informationen enthält, die nicht offensichtlich sind. Aber wirklich, dies ist kein Argument gegen veränderbare Strukturen, wie manche behaupten. Ja, was wir hier sehen, ist eine "Grube der Verzweiflung", wie Eric es ausgedrückt hätte, aber die Quelle dieser Verzweiflung ist nicht Veränderlichkeit. Die Quelle der Verzweiflung sind die selbstmutierenden Methoden der Strukturen . (Warum sich Arrays und Listen unterschiedlich verhalten, liegt daran, dass einer im Grunde ein Operator ist, der eine Speicheradresse berechnet, und der andere eine Eigenschaft. Im Allgemeinen wird alles klar, sobald Sie verstehen, dass eine "Referenz" ein Adresswert ist .)
AnorZaken

18

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.


Klassenfelder von Strukturtypen zu haben, kann oft ein sehr nützliches Muster sein, obwohl es zugegebenermaßen einige Einschränkungen gibt. Die Leistung wird beeinträchtigt, wenn man Eigenschaften anstelle von Feldern verwendet oder verwendet 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 wie int[]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.
Supercat

13

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 Pythonnamedtuple ), 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:

point.x = point.x + 1

verglichen mit:

point = Point(point.x + 1, point.y)

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.


12

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


3
Richtig, aber in diesem Fall liegt der Grund für die Verwendung einer Struktur darin, dass dies dem Anwendungsdesign durch externe Einschränkungen (die durch die Verwendung des nativen Codes) auferlegt werden. Alles andere, was Sie über diese Objekte beschreiben, legt nahe, dass es sich eindeutig um Klassen in C # oder VB.NET handeln sollte.
Jon Hanna

6
Ich bin mir nicht sicher, warum manche Leute denken, die Dinge sollten Klassenobjekte sein. Wenn alle Array-Slots mit Verweisen unterschiedlicher Instanzen gefüllt sind, erhöht die Verwendung eines Klassentyps die Speicheranforderung um zusätzliche zwölf oder vierundzwanzig Bytes, und der sequentielle Zugriff auf ein Array von Klassenobjektreferenzen ist wahrscheinlich viel langsamer als der sequentielle Zugriff auf eine Reihe von Strukturen.
Supercat

9

Wenn etwas mutiert werden kann, gewinnt es ein Gefühl der Identität.

struct Person {
    public string name; // mutable
    public Point position = new Point(0, 0); // mutable

    public Person(string name, Point position) { ... }
}

Person eric = new Person("Eric Lippert", new Point(4, 2));

Da Persones 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 ändern eric.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, Personmuss daran denken, nach einem Hinweis zu fragen, Personsonst 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 ich ericeinen Klon von erhalte oder nicht eric. Im Allgemeinen kann ein Typ sicher als Wert übergeben werden, wenn sein gesamter beobachtbarer Status in Mitgliedern gespeichert ist, die entweder:

  • unveränderlich
  • Referenztypen
  • sicher durch Wert zu übergeben

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 Personhängt jedoch davon ab, was Sie versuchen. Wenn es sich Personnur um einen Datensatz über eine Person handelt, ist daran nichts Unintuitives. PersonVariablen repräsentieren wirklich abstrakte Werte , keine Objekte. (In diesem Fall wäre es wahrscheinlich angemessener, es umzubenennen PersonData.) Wenn Sie Persontatsä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 ericihn 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.


3
Ihr Standpunkt zur Identität ist gut; Es kann erwähnenswert sein, dass Identität nur relevant ist, wenn mehrere Verweise auf etwas existieren. Wenn fooder einzige Verweis auf sein Ziel irgendwo im Universum vorhanden ist und nichts den Identitäts-Hash-Wert dieses Objekts erfasst hat, entspricht das Mutationsfeld foo.Xsemantisch dem Verweisen auf fooein neues Objekt, das genau dem entspricht, auf das es zuvor Bezug genommen hat, jedoch mit XHalten 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.
Supercat

1
Wenn Thingein veränderlicher Klasse - Typ ist, eine Thing[] wird Objektidentitäten kapseln - ob man es will oder nicht - es sei denn , man , dass nicht sicherstellen kann , Thingin 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. Wenn Thinges sich um eine Struktur handelt, Thing[]kapselt a nur Werte.
Supercat

Bei Objekten kommt ihre Identität von ihrem Standort. Die Instanzen von Referenztypen haben ihre Identität dank ihrer Position im Speicher, und Sie geben nur ihre Identität (eine Referenz) weiter, nicht ihre Daten, während Werttypen ihre Identität an dem äußeren Ort haben, an dem sie gespeichert sind. Die Identität Ihres Eric-Werttyps stammt nur von der Variablen, in der er gespeichert ist. Wenn Sie ihn weitergeben, verliert er seine Identität.
IllidanS4 will Monica

6

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.


3
Dies gilt auch, wenn Sie eine Klasse als Schlüssel in einer Karte verwenden.
Marc Gravell

6

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.


1
Direkt auf den Punkt! Strukturen sind Organisationseinheiten ohne Zugangskontrollbeschränkungen! Leider hat C # sie für diesen Zweck unbrauchbar gemacht!
ThunderGr

Dies geht völlig daneben , da beide Beispiele veränderbare Strukturen zeigen.
Vidstige

C # machte sie für diesen Zweck unbrauchbar, weil das nicht der Zweck der Strukturen ist
Luiz Felipe

6

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.

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

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

struct Mutable {
public int x;
}

class Test {
    private Mutable m = new Mutable();
    public int mutate()
    { 
        m.x = m.x + 1;
        return m.x;
    }
  }
  static void Main(string[] args) {
        Test t = new Test();
        System.Console.WriteLine(t.mutate());
        System.Console.WriteLine(t.mutate());
        System.Console.WriteLine(t.mutate());
    }

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


4
Es ist nichts Falsches daran, kleine undurchsichtige Strukturen so zu entwerfen, dass sie sich wie unveränderliche Klassenobjekte verhalten. Die MSDN-Richtlinien sind sinnvoll, wenn versucht wird, etwas zu erstellen, das sich wie ein Objekt verhält . Strukturen sind in einigen Fällen geeignet, in denen man leichte Dinge benötigt, die sich wie Objekte verhalten, und in Fällen, in denen man eine Reihe von Variablen benötigt, die mit Klebeband zusammengeklebt sind. Aus irgendeinem Grund erkennen viele Menschen jedoch nicht, dass Strukturen zwei unterschiedliche Verwendungszwecke haben und dass Richtlinien, die für eine geeignet sind, für die andere unangemessen sind.
Supercat

5

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.


Strukturen werden in c # nicht als Alias ​​verwendet. Jede Strukturzuweisung ist eine Kopie.
rekursiv

@recursive: In einigen Fällen ist dies ein großer Vorteil von veränderlichen Strukturen, und einer, der mich dazu bringt, die Vorstellung in Frage zu stellen, dass Strukturen nicht veränderbar sein sollten. Die Tatsache, dass Compiler manchmal implizit Strukturen kopieren, verringert nicht die Nützlichkeit veränderbarer Strukturen.
Supercat

1

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.


Aus meiner Sicht ist eine Struktur das Herzstück einer Reihe von Variablen, die mit Klebeband zusammengeklebt sind. In .NET ist es möglich, dass eine Struktur vorgibt, etwas anderes als eine Reihe von Variablen zu sein, die mit Klebeband zusammengeklebt sind, und ich würde vorschlagen, dass ein Typ, der vorgibt, etwas anderes als eine Reihe von Variablen zu sein, wenn dies praktikabel ist mit Klebeband sollte sich wie ein einheitliches Objekt verhalten (was für eine Struktur Unveränderlichkeit bedeuten würde), aber manchmal ist es nützlich, eine Reihe von Variablen mit Klebeband zusammenzuhalten. Selbst im Produktionscode würde ich es für besser halten, einen Typ zu haben ...
supercat

... die eindeutig keine Semantik mehr hat als "jedes Feld enthält das letzte, was darauf geschrieben wurde", wodurch alle Semantik in den Code verschoben wird, der die Struktur verwendet, als zu versuchen, eine Struktur mehr tun zu lassen. Wenn beispielsweise ein Range<T>Typ mit Elementen Minimumund MaximumFeldern vom Typ Tund Code angegeben wird Range<double> myRange = foo.getRange();, sollten alle Garantien darüber, was Minimumund was Maximumenthalten, stammen foo.GetRange();. Nachdem Rangewäre eine belichteter-Feld - Struktur deutlich machen , dass es wird kein Verhalten der eigenen hinzufügen.
Supercat
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.