555 Stimmen

Warum sind veränderbare Strukturen "böse"?

Wenn ich die Diskussionen hier auf SO verfolge, habe ich schon mehrmals die Bemerkung gelesen, dass mutable structs "böse" sind (wie in der Antwort auf diese Frage ).

Was ist das eigentliche Problem mit Mutability und Structs in C#?

25 Stimmen

Die Behauptung, veränderbare Strukturen seien böse, ist wie die Behauptung, veränderbare int s, bool s, und alle anderen Werttypen sind böse. Es gibt Fälle für Veränderbarkeit und für Unveränderbarkeit. Diese Fälle hängen von der Rolle ab, die die Daten spielen, nicht von der Art der Speicherzuweisung/-freigabe.

63 Stimmen

@slipp int y bool sind pas wandelbar..

2 Stimmen

. -Syntax, wodurch Operationen mit ref-typed data und value-typed data gleich aussehen, obwohl sie sich deutlich unterscheiden. Dies ist ein Fehler von C#s Eigenschaften, nicht von Structs - einige Sprachen bieten eine alternative a[V][X] = 3.14 Syntax für In-Place-Mutationen. In C# ist es besser, Struktur-Member-Mutator-Methoden wie 'MutateV(Action<ref Vector2> mutator)` anzubieten und sie wie folgt zu verwenden a.MutateV((v) => { v.X = 3; }) (Das Beispiel ist aufgrund der Einschränkungen, die C# hinsichtlich der ref Schlüsselwort, sollte aber mit einigen Umgehungen möglich sein) .

27voto

Sergey Teplyakov Punkte 11077

Es gibt noch ein paar andere Eckfälle, die aus der Sicht des Programmierers zu unvorhersehbarem Verhalten führen können.

Unveränderliche Wertetypen 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änderliche Wertetypen und Array

Angenommen, wir haben ein Array unserer Mutable Struktur und wir rufen die IncrementI Methode für das erste Element dieses Arrays. Welches Verhalten erwarten Sie von diesem Aufruf? Soll er den Wert des Arrays ändern oder nur eine Kopie erstellen?

    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 nichts Schlechtes, solange Sie und der Rest des Teams genau wissen, was Sie tun. Aber es gibt zu viele Eckfälle, in denen sich das Programm anders verhält als erwartet, was zu subtilen, schwer zu produzierenden und schwer zu verstehenden Fehlern führen kann.

23voto

Morten Christiansen Punkte 18236

Wertetypen stellen im Grunde unveränderliche Konzepte dar. Es macht keinen Sinn, einen mathematischen Wert wie eine ganze Zahl, einen Vektor usw. zu haben und ihn dann ändern zu können. Das käme einer Neudefinition der Bedeutung eines Wertes gleich. Anstatt einen Werttyp zu ändern, ist es sinnvoller, einen anderen eindeutigen Wert zuzuweisen. Denken Sie daran, dass Wertetypen verglichen werden, indem alle Werte ihrer Eigenschaften verglichen werden. Wenn die Eigenschaften gleich sind, handelt es sich um die gleiche universelle Darstellung des Wertes.

Wie Konrad erwähnt, macht es auch keinen Sinn, ein Datum zu ändern, da der Wert einen eindeutigen Zeitpunkt darstellt und nicht eine Instanz eines Zeitobjekts, das einen Zustand oder eine Kontextabhängigkeit aufweist.

Ich hoffe, das ergibt für Sie einen Sinn. Es geht mehr um das Konzept, das Sie mit Werttypen zu erfassen versuchen, als um praktische Details, das ist sicher.

19voto

ThunderGr Punkte 2199

Wenn Sie jemals in einer Sprache wie C/C++ programmiert haben, sind Structs als veränderbar gut zu verwenden. Ü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 dass ich in einigen Fällen nicht in der Lage bin, das dumme Ding zu zwingen, einen Verweis auf die Struktur zu verwenden, anstatt eine Kopie (wie wenn eine Struktur Teil einer C#-Klasse ist).

Mutable Structs sind also nicht böse, C# hat gemacht sie böse. Ich verwende veränderbare Strukturen in C++ die ganze Zeit und sie sind sehr bequem und intuitiv. Im Gegensatz dazu hat mich C# dazu gebracht, Structs als Mitglieder von Klassen aufgrund der Art und Weise, wie sie mit Objekten umgehen, vollständig aufzugeben. Ihre Bequemlichkeit hat uns die unsere gekostet.

14voto

Hugo Punkte 139

Stellen Sie sich vor, Sie haben ein Array mit 1.000.000 Structs. Jede Struktur repräsentiert eine Aktie mit Dingen wie bid_price, offer_price (vielleicht Dezimalzahlen) und so weiter, dies wird von C#/VB erstellt.

Stellen Sie sich vor, dass das Array in einem Speicherblock erstellt wird, der im nicht verwalteten Heap zugewiesen ist, so dass ein anderer nativer Code-Thread gleichzeitig auf das Array zugreifen kann (vielleicht ein High-Perf-Code, der mathematische Berechnungen durchführt).

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

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

Nun, lassen Sie uns den Tatsachen ins Auge sehen, in diesem Fall wollen wir wirklich, dass diese Strukturen veränderbar sind, sie müssen sein, weil sie von einigen anderen nativen Code geteilt werden, so dass die Erstellung von Kopien nicht helfen wird; Sie müssen sein, weil eine Kopie von einigen 120 Byte Struktur bei diesen Raten ist Wahnsinn, vor allem, wenn ein Update tatsächlich nur ein oder zwei Byte auswirken kann.

Hugo

14voto

Luis Masuelli Punkte 11452

Wenn Sie sich daran halten, wofür Structs gedacht sind (in C#, Visual Basic 6, Pascal/Delphi, C++ struct type (oder Klassen), wenn sie nicht als Zeiger verwendet werden), werden Sie feststellen, dass eine Struktur nicht mehr ist als ein zusammengesetzte Variable . Das bedeutet, dass Sie sie als einen gepackten Satz von Variablen unter einem gemeinsamen Namen behandeln (eine Datensatzvariable, auf deren Mitglieder Sie verweisen).

Ich weiß, dass das viele Leute verwirren würde, die an OOP gewöhnt sind, aber das ist kein ausreichender Grund, um zu sagen, dass solche Dinge von Natur aus schlecht sind, wenn sie richtig eingesetzt werden. Einige Strukturen sind unveränderlich, so wie sie beabsichtigt sind (dies ist der Fall von Pythons namedtuple ), aber es ist ein weiteres Paradigma, das es zu berücksichtigen gilt.

Ja: Strukturen erfordern eine Menge Speicher, aber es wird nicht genau mehr Speicher zu tun:

point.x = point.x + 1

verglichen mit:

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

Der Speicherverbrauch ist mindestens genauso hoch, im unveränderlichen Fall sogar höher (obwohl dieser Fall je nach Sprache für den aktuellen Stack nur vorübergehend wäre).

Aber schließlich sind die Strukturen Strukturen und nicht Objekte. In POO ist die wichtigste Eigenschaft eines Objekts seine Identität die in den meisten Fällen nicht größer ist als die Speicheradresse. Struct steht für Datenstruktur (kein richtiges Objekt, und daher haben sie auch keine Identität), und Daten können geändert werden. In anderen Sprachen, Eintrag (anstelle von Struktur (wie bei Pascal) ist das Wort und hat den gleichen Zweck: eine Datensatzvariable, die aus Dateien gelesen, verändert und in Dateien ausgegeben werden kann (das ist die Hauptverwendung und in vielen Sprachen kann man sogar die Datenausrichtung im Datensatz definieren, während das bei richtig benannten Objekten nicht unbedingt der Fall ist).

Sie wollen ein gutes Beispiel? Structs werden verwendet, um Dateien einfach zu lesen. Python hat diese Bibliothek da es objektorientiert ist und keine Unterstützung für Structs hat, musste es auf eine andere Art und Weise implementiert werden, die etwas hässlich ist. Sprachen, die structs implementieren, haben diese Funktion... eingebaut. Versuchen Sie einmal, einen Bitmap-Header mit einer entsprechenden Struktur in Sprachen wie Pascal oder C zu lesen. Es wird einfach sein (wenn die Struktur richtig aufgebaut und ausgerichtet ist; in Pascal würden Sie keinen Record-basierten Zugriff verwenden, sondern Funktionen, um beliebige Binärdaten zu lesen). Für Dateien und direkten (lokalen) Speicherzugriff sind Structs also besser als Objekte. Heutzutage sind wir an JSON und XML gewöhnt und vergessen daher die Verwendung von Binärdateien (und als Nebeneffekt auch die Verwendung von Structs). Aber ja: es gibt sie, und sie haben einen Zweck.

Sie sind nicht böse. Man muss sie nur für den richtigen Zweck einsetzen.

Wenn Sie in Hämmern denken, werden Sie Schrauben wie Nägel behandeln wollen, um festzustellen, dass Schrauben schwieriger in die Wand einzudringen sind, und dass es die Schuld der Schrauben ist, und sie werden die Bösen sein.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X