it-swarm.com.de

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

Im Anschluss an die 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 der Veränderbarkeit und den Strukturen in C #?

467
Dirk Vollmar

Strukturen sind Werttypen, dh sie werden beim Weitergeben kopiert.

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 als Wert 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)

279
trampster

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 Dinge aus einer Liste zu bekommen:

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

was hat sich dadurch geändert? Nichts nützliches ...

Das selbe mit Eigenschaften:

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

dich zwingen zu tun:

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

weniger kritisch gibt es ein Größenproblem; veränderliche Objekte neigen dazu, mehrere Eigenschaften zu haben; Wenn Sie jedoch eine Struktur mit zwei ints, einem string, einem DateTime und einem bool haben, können Sie sehr schnell eine Menge Speicher durchbrennen. Mit einer Klasse können mehrere Aufrufer einen Verweis auf dieselbe Instanz freigeben (Verweise sind klein).

165
Marc Gravell

Ich würde nicht evil sagen, aber Mutabilität ist oft ein Zeichen von Überanstrengung seitens des Programmierers, um ein Maximum an Funktionalität bereitzustellen. In der Realität wird dies häufig nicht benötigt, was wiederum die Benutzeroberfläche kleiner, benutzerfreundlicher und fehlerhafter macht (= robuster).

Ein Beispiel hierfür sind Lese-/Schreib- und Schreib-/Schreibkonflikte unter Rennbedingungen. Diese können in unveränderlichen Strukturen einfach nicht vorkommen, da ein Schreibvorgang keine gültige Operation ist.

Auch behaupte ich, dass Wandlungsfähigkeit eigentlich fast nie benötigt wird , der Programmierer nur denkt nach dass es könnte in der Zukunft sein. Zum Beispiel ist es einfach nicht sinnvoll, ein Datum zu ändern. Erstellen Sie stattdessen ein neues Datum, das auf dem alten basiert. Dies ist ein billiger Vorgang, daher spielt die Leistung keine Rolle.

69
Konrad Rudolph

Veränderbare 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 perfekt 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 Members eines Werttyps oder eines Referenztyps zu unterscheiden. Daher bin ich für bevorzugt unveränderliche Strukturen, die die Unveränderlichkeit erzwingen, gegenüber veränderlichen structs.

Anstatt unveränderliche Strukturen einfach als "böse" zu bezeichnen, würde ich raten, die Sprache anzunehmen und eine hilfreichere und konstruktivere Faustregel zu befürworten.

Zum Beispiel: "Strukturen sind Werttypen, die standardmäßig kopiert werden. Sie benötigen einen Verweis, wenn Sie sie nicht kopieren möchten" oder "versuchen Sie zuerst, mit schreibgeschützten Strukturen zu arbeiten").

43
JE42

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. Struct-Methoden, die "dies" nicht mutieren, sollten auch für schreibgeschützte Strukturen ohne die Notwendigkeit eines defensiven Kopierens verfügbar sein. Methoden, die "dies" mutieren, sollten bei schreibgeschützten 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ützten Strukturen. nur Kontexte, die wohl das Schlimmste von beiden Welten bekommen.

Trotz der Probleme beim Umgang mit selbstmutierenden Methoden in schreibgeschützten Kontexten bieten veränderliche 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;}; 
 class PointyClass {public int x, y, z;}; 
 
 void Method1 (PointyStruct foo); 
 Methode2 ungültig machen (siehe PointyStruct foo); 
 Methode3 ungültig machen (PointyClass foo); 

Beantworten Sie für jede Methode die folgenden Fragen:

  1. Unter der Annahme, dass die Methode keinen "unsicheren" Code verwendet, könnte sie foo ändern?
  2. Wenn vor dem Aufruf der Methode keine externen Verweise auf 'foo' vorhanden sind, kann dann ein externer Verweis vorhanden sein?

Antworten:

Frage 1:
Method1(): no (klare Absicht)
Method2(): yes (klare Absicht)
Method3(): yes (ungewisse Absicht)
Frage 2:
Method1(): nein
Method2(): no (sofern nicht unsicher)
Method3(): ja

Methode1 kann foo nicht ändern und bekommt nie eine Referenz. Method2 erhält eine kurzlebige Referenz auf foo, die es verwenden kann, um die Felder von foo beliebig oft und in beliebiger Reihenfolge zu ändern, 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 angefertigt wurden, verschwunden, es sei denn, sie verwendet unsicheren Code. Im Gegensatz zu Methode2 erhält Methode3 einen promisku gemeinsam nutzbaren Verweis auf foo, und es ist nicht abzusehen, was es damit machen könnte. Möglicherweise ändert sich foo überhaupt nicht, es ändert sich foo und kehrt dann zurück, oder es verweist auf foo auf einen anderen Thread, der es zu einem beliebigen späteren Zeitpunkt auf eine beliebige Weise mutiert. Die einzige Möglichkeit, zu begrenzen, was Method3 mit einem übergebenen veränderlichen Klassenobjekt tun kann, besteht darin, das veränderliche Objekt in einem schreibgeschützten Wrapper zu kapseln, der hässlich und umständlich ist.

Arrays von Strukturen bieten wunderbare Semantik. Bei gegebenem RectArray [500] vom Typ Rectangle ist es klar und offensichtlich, wie z. Kopieren Sie das Element 123 in das Element 456 und setzen Sie die Breite des Elements 123 einige Zeit später auf 555, 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 mit einem 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;". Vielleicht wird davon ausgegangen, dass das Array Instanzen enthält, die sich nicht ändern werden, sodass der richtige Code eher wie folgt lautet: "RectClassArray [321] = RectClassArray [456]; ...; RectClassArray [321] = New RectClass (RectClassArray [321 ]); RectClassArray [321] .X = 555; " Um zu wissen, was man tun soll, muss man viel mehr über RectClass wissen (unterstützt es beispielsweise einen Kopierkonstruktor, eine Copy-from-Methode usw.) und die beabsichtigte Verwendung des Arrays. Nicht annähernd so sauber wie mit einer Struktur.

Leider gibt es für keine andere Containerklasse als ein Array eine gute Möglichkeit, die saubere Semantik eines struct-Arrays zu bieten. Das Beste, was man tun kann, wenn man möchte, dass eine Sammlung mit z. Eine Zeichenfolge würde wahrscheinlich darin bestehen, eine generische "ActOnItem" -Methode anzubieten, die eine Zeichenfolge für den Index, einen generischen Parameter und einen Delegaten akzeptiert, der unter Bezugnahme sowohl auf den generischen Parameter als auch auf das Auflistungselement übergeben wird. Das würde fast die gleiche Semantik wie bei Struct-Arrays ermöglichen, aber wenn nicht versucht werden kann, eine Nice-Syntax für vb.net und C # bereitzustellen, sieht der Code klobig aus, selbst wenn er eine angemessene Leistung aufweist (Übergabe eines generischen Parameters) Ermöglichen Sie die Verwendung eines statischen Delegaten und vermeiden Sie die Notwendigkeit, temporäre Klasseninstanzen zu erstellen.

Ich persönlich bin verärgert über den Hass von Eric Lippert et al. spucken in Bezug auf veränderbare Werttypen. Sie bieten eine viel sauberere Semantik als die vielzähligen 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 sich veränderbare Werttypen besser eignen als jede andere Art von Entität.

38
supercat

Werttypen repräsentieren grundsätzlich unveränderliche Konzepte. Fx macht es 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 Werttyp 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 Zustand oder eine Kontextabhängigkeit aufweist.

Hoffentlich ergibt das für Sie keinen Sinn. Es geht eher um das Konzept, das Sie mit Werttypen erfassen möchten, als um praktische Details.

23

Es gibt noch ein paar Eckfälle, die aus Sicht der Programmierer zu unvorhersehbarem Verhalten führen können. Hier ein paar von ihnen.

  1. 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);
    }
}
</ code>
  1. Mutable Werttypen und Array

Nehmen wir an, wir haben ein Array unserer Mutable-Struktur und rufen die IncrementI-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?

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änderbare 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 dem erwarteten unterscheidet, was zu subtilen, schwer zu erzeugenden und schwer zu verstehenden Fehlern führen kann.

18

Wenn Sie jemals in einer Sprache wie C/C++ programmiert haben, können Strukturen problemlos als veränderbar verwendet werden. Übergeben Sie sie einfach mit ref, um 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 nicht dazu zwingen, einen Verweis auf die Struktur anstelle einer Kopie zu verwenden (wie wenn eine Struktur Teil einer C # -Klasse ist) ).

Also, veränderbare Strukturen sind nicht böse, C # hat sie böse gemacht . In C++ verwende ich ständig veränderbare Strukturen, die sehr praktisch und intuitiv sind. Im Gegensatz dazu habe ich in C # Strukturen als Member von Klassen aufgrund der Art und Weise, wie sie mit Objekten umgehen, vollständig aufgegeben. Ihre Bequemlichkeit hat uns unsere gekostet.

17
ThunderGr

Wenn Sie sich an die Struktur halten, für die sie bestimmt ist (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 - ist. zusammengesetzte Variable. Das bedeutet: Sie behandeln sie als gepackten Satz von Variablen unter einem gemeinsamen Namen (eine Datensatzvariable, von der Sie auf Mitglieder verweisen).

Ich weiß, das würde viele Leute verwirren, die tief an OOP gewöhnt sind, aber das ist nicht genug Grund zu der Annahme, dass solche Dinge von Natur aus böse sind, wenn sie richtig angewendet werden. Einige Strukturen sind unveränderlich, wie sie beabsichtigt sind (dies ist der Fall von Pythons namedtuple), aber es ist ein anderes zu berücksichtigendes Paradigma.

Ja: Strukturen benötigen 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 mindestens der gleiche oder im unveränderlichen Fall sogar noch höher (obwohl dieser Fall für den aktuellen Stapel je nach Sprache nur vorübergehend wäre).

Aber schließlich sind Strukturen Strukturen, keine Objekte. In POO ist die Haupteigenschaft eines Objekts Identität, was meistens nicht mehr als seine Speicheradresse ist. Struct steht für Datenstruktur (kein richtiges Objekt, daher haben sie ohnehin 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 erfüllt denselben Zweck: Nur eine Datensatzvariable, die zum Lesen vorgesehen ist aus Dateien, die modifiziert und in Dateien gespeichert wurden (dies ist die Hauptverwendung 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? Mit Structs können Dateien einfach gelesen werden. Python hat diese Bibliothek weil es objektorientiert ist und keine Unterstützung für Strukturen hat, musste es auf eine andere Art implementiert werden, was etwas hässlich ist. Languages Bei der Implementierung von Strukturen ist diese Funktion bereits integriert. Versuchen Sie, einen Bitmap-Header mit einer geeigneten Struktur in Sprachen wie Pascal oder C zu lesen. In Pascal ist es einfach (wenn die Struktur ordnungsgemäß erstellt und ausgerichtet ist), keinen Datensatz zu verwenden Zugriff, aber Funktionen zum Lesen beliebiger Binärdaten.) Bei Dateien und direktem (lokalem) Speicherzugriff sind Strukturen also besser als Objekte. Heutzutage 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 einzutauchen sind, und es wird die Schuld der Schrauben sein, und es werden die bösen sein.

12
Luis Masuelli

Stellen Sie sich vor, Sie haben ein Array von 1.000.000 Strukturen. Jede Struktur, die eine Equity darstellt, mit Dingen wie bid_price, offer_price (möglicherweise Dezimalzahlen) usw., wird von C #/VB erstellt.

Stellen Sie sich vor, das Array wird in einem Speicherblock erstellt, der im nicht verwalteten Heap zugewiesen ist, sodass ein anderer Thread mit systemeigenem Code gleichzeitig auf das Array zugreifen kann (möglicherweise ein hochleistungsfähiger Code, der mathematische Aufgaben ausführt).

Stellen Sie sich vor, der C #/VB-Code hört einen Marktfeed von Preisänderungen ab. Dieser Code muss möglicherweise auf ein Element des Arrays zugreifen (für welche Sicherheit auch immer) und dann ein oder mehrere Preisfelder ändern.

Stellen Sie sich vor, dies geschieht zehn- oder sogar hunderttausendmal pro Sekunde.

Nun, lassen Sie uns Tatsachen ins Auge sehen. In diesem Fall wollen wir wirklich, dass diese Strukturen veränderbar sind. Sie müssen es sein, weil sie von einem anderen nativen Code gemeinsam genutzt werden. Dies muss der Fall sein, da das Erstellen einer Kopie von etwa 120-Byte-Strukturen mit diesen Raten verrückt ist, insbesondere wenn sich ein Update möglicherweise nur auf ein oder zwei Bytes auswirkt.

Hugo

12
Hugo

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 Person veränderlich ist, ist es natürlicher, über Erics Position ändern nachzudenken als Eric klonen, den Klon verschieben und das Original zerstören. Beiden Operationen würde es gelingen, den Inhalt von eric.position, aber einer ist intuitiver als der andere. Ebenso ist es intuitiver, Eric (als Referenz) für Methoden zum Ändern von Eric weiterzugeben. Es wird fast immer überraschen, einer Methode einen Klon von Eric zu geben. Wer Person mutieren will, muss daran denken, nach einem Verweis auf Person zu fragen, sonst machen sie das Falsche.

Wenn Sie den Typ unveränderlich machen, verschwindet das Problem. Wenn ich eric nicht ändern kann, spielt es für mich keine Rolle, ob ich eric oder einen Klon von eric erhalte. Im Allgemeinen kann ein Typ sicher als Wert übergeben werden, wenn sein gesamter beobachtbarer Zustand in Mitgliedern enthalten ist, die entweder:

  • unveränderlich
  • referenztypen
  • sicher durch Wert übergeben

Wenn diese Bedingungen erfüllt sind, verhält sich ein Typ mit veränderlichen Werten wie ein Referenztyp, da eine flache Kopie es dem Empfänger weiterhin ermöglicht, die ursprünglichen Daten zu ändern.

Die Intuitivität eines unveränderlichen Person hängt jedoch davon ab, was Sie versuchen. Wenn Person nur ein Satz von Daten über eine Person darstellt, ist nichts Unintuitives daran; Person Variablen repräsentieren wirklich abstrakt Werte, keine Objekte. (In diesem Fall wäre es wahrscheinlich sinnvoller, es in PersonData umzubenennen.) Wenn Person tatsächlich eine Person selbst modelliert, ist die Idee, ständig Klone zu erstellen und zu verschieben, albern, selbst wenn Sie haben die Gefahr vermieden, das Original zu verändern. In diesem Fall wäre es wahrscheinlich natürlicher, Person einfach als Referenztyp (dh als Klasse) zu definieren.

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 halten und ihn mutieren), aber da dies nicht idiomatisch ist OOP es wird für jeden, der mit Ihrem Code arbeitet, immer noch uninteressant sein.

8
Doval

Persönlich, wenn ich mir Code ansehe, erscheint mir Folgendes ziemlich klobig:

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 öffentliche Funktionen festgelegt haben und Funktionen abrufen, die nur den Wert für den jeweils übergebenen Wert festlegen, wie ist dies eine Verbesserung gegenüber dem einfachen Weitergeben einer öffentlichen Datenstruktur?

Beim Erstellen einer privaten Struktur innerhalb einer Klasse 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 zu erhalten und neue Instanzen zu erstellen.

Für mich verhindert dies, dass eine gültige Verwendung von Strukturen zum Organisieren öffentlicher Variablen verwendet wird, wenn ich eine Zugriffssteuerung möchte, würde ich eine Klasse verwenden.

6
Mike

Es gibt mehrere Probleme mit dem Beispiel von Herrn Eric Lippert. Es soll veranschaulichen, dass 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 sollte nur öffentliche Mitglieder haben und keine Kapselung erfordern. Wenn ja, dann sollte es wirklich ein Typ/eine Klasse sein. Sie brauchen wirklich keine 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 wandelbar sein, das ist die Idee und Absicht.

Das Ergebnis der Änderungen von voila verhält sich wie erwartet:

1 2 3 Drücken Sie eine beliebige Taste, um fortzufahren. . .

6
Ramesh Kadambi

Es hat nichts mit Strukturen zu tun (und auch nicht mit C #), aber in Java) können Probleme mit veränderlichen Objekten auftreten, wenn sie z. B. Schlüssel in einer Hash-Map sind Nach dem Hinzufügen zu einer Karte und dem Ändern des Hash-Codes können böse Dinge passieren.

6
Bombe

Veränderliche 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 magisch auf die anderen Stellen geändert zu haben, die ihn verwenden. Dies hängt mit den Rennbedingungen zusammen, ist jedoch nicht mit diesen identisch.

Der Millionen-Dollar-Vorteil ist manchmal Modularität. Der veränderbare Status kann es Ihnen ermöglichen, sich ändernde Informationen vor Code zu verbergen, der nichts davon wissen muss.

Die Kunst des Dolmetschers geht detailliert auf diese Kompromisse ein und gibt einige Beispiele.

5
Andru Luvisi

Ich glaube nicht, dass sie böse sind, wenn sie richtig verwendet werden. Ich würde es nicht in meinen Produktionscode einfügen, aber ich würde es für so etwas wie strukturierte Unit-Testing-Mocks verwenden, bei denen die Lebensdauer einer Struktur relativ gering ist.

Wenn Sie das Eric-Beispiel verwenden, 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 älteren Code zu testen oder zu ändern, der ein bestimmtes Objekt (den Punkt von Strukturen) flach definiert. Wenn Sie jedoch eine unveränderbare Struktur haben, wird die Verwendung störend verhindert.

1
ShaunnyBwoy