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

10voto

Doval Punkte 2376

Wenn etwas mutiert werden kann, erhält es eine 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));

Denn Person veränderlich ist, ist es natürlicher, über Erics Position ändern als Eric klonen, den Klon verschieben und das Original zerstören . Beide Vorgänge würden den Inhalt von eric.position aber das eine ist intuitiver als das andere. Ebenso ist es intuitiver, Eric weiterzugeben (als Referenz), damit die Methoden ihn ändern können. Einer Methode einen Klon von Eric zu geben, wird fast immer überraschend sein. Jeder, der mutieren möchte Person muss daran denken, um eine Referenz zu bitten Person oder sie tun 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 eric oder ein Klon von eric . Allgemeiner ausgedrückt: Ein Typ kann sicher als Wert übergeben werden, wenn sein gesamter beobachtbarer Zustand in Elementen enthalten ist, die entweder:

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

Wenn diese Bedingungen erfüllt sind, verhält sich ein veränderlicher Werttyp wie ein Referenztyp, da eine oberflächliche Kopie dem Empfänger immer noch erlaubt, die ursprünglichen Daten zu ändern.

Die Intuitivität einer unveränderlichen Person Das hängt allerdings davon ab, was Sie erreichen wollen. Wenn Person stellt lediglich eine Datensatz über eine Person, so ist das nicht unintuitiv; Person Variablen wirklich abstrakt darstellen Werte und nicht Objekte. (In diesem Fall wäre es wahrscheinlich angemessener, ihn umzubenennen in PersonData .) Wenn Person tatsächlich eine Person selbst modelliert, ist die Idee, ständig Klone zu erstellen und zu verschieben, albern, selbst wenn man den Fehler vermieden hat, zu denken, man würde das Original verändern. In diesem Fall wäre es wahrscheinlich sinnvoller, einfach Person einen Referenztyp (d.h. eine Klasse).

Zugegeben, wie uns die funktionale Programmierung gelehrt hat, gibt es Vorteile, wenn man alles unveränderlich (niemand kann heimlich einen Verweis auf eric und ihn mutieren), aber da das in der OOP nicht idiomatisch ist, wird es für alle anderen, die mit Ihrem Code arbeiten, trotzdem unintuitiv sein.

6voto

Bombe Punkte 77831

Es hat zwar nichts mit Structs zu tun (und auch nicht mit C#), aber in Java kann es zu Problemen mit veränderlichen Objekten kommen, wenn sie z.B. Schlüssel in einer Hash-Map sind. Wenn Sie sie ändern, nachdem Sie sie zu einer Map hinzugefügt haben, und die Map ändert ihre [Hash-Code](http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Object.html#hashCode()) könnten böse Dinge passieren.

6voto

Andru Luvisi Punkte 23151

Es gibt viele Vor- und Nachteile von veränderlichen Daten. Der millionenschwere Nachteil ist die Verfremdung. Wenn derselbe Wert an mehreren Stellen verwendet wird und an einer davon geändert wird, scheint er sich an den anderen Stellen, die ihn verwenden, auf magische Weise geändert zu haben. Dies ist mit Race Conditions verwandt, aber nicht identisch mit ihnen.

Der Millionen-Dollar-Vorteil ist die Modularität, manchmal. Ein veränderlicher Zustand kann es Ihnen ermöglichen, sich ändernde Informationen vor Code zu verbergen, der sie nicht kennen muss.

Die Kunst des Dolmetschers geht ausführlich auf diese Kompromisse ein und gibt einige Beispiele.

6voto

Mike Punkte 79

Wenn ich mir den Code ansehe, sieht das Folgende für mich persönlich ziemlich klobig aus:

data.value.set ( data.value.get () + 1 ) ;

und nicht einfach

data.value++ ; oder data.value = data.value + 1 ;

Die Datenkapselung ist nützlich, wenn Sie eine Klasse weitergeben und sicherstellen wollen, dass der Wert auf kontrollierte Weise geändert wird. Wenn Sie jedoch öffentliche "set"- und "get"-Funktionen haben, die kaum mehr tun, als den Wert auf das zu setzen, was auch immer übergeben wird, wie kann dies eine Verbesserung gegenüber der einfachen Weitergabe einer öffentlichen Datenstruktur sein?

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 diese Struktur innerhalb des Klassenbereichs ändern können und nicht Kopien dieser Struktur erhalten und neue Instanzen erstellen.

Für mich verhindert dies eine gültige Verwendung von Strukturen, die zur Organisation öffentlicher Variablen verwendet werden; wenn ich Zugriffskontrolle wollte, würde ich eine Klasse verwenden.

6voto

Ramesh Kadambi Punkte 526

Es gibt mehrere Probleme mit dem Beispiel von Herrn Eric Lippert. Es ist konstruiert, um zu verdeutlichen, dass Strukturen kopiert werden und dass das ein Problem sein kann, wenn man nicht vorsichtig ist. Wenn ich mir das Beispiel ansehe, sehe ich es als Ergebnis einer schlechten Programmiergewohnheit und nicht wirklich als ein Problem mit der Struktur oder der Klasse.

  1. Eine Struktur soll nur öffentliche Mitglieder haben und sollte keine Kapselung erfordern. Wenn doch, dann sollte es wirklich ein Typ/Klasse sein. Man braucht wirklich nicht zwei Konstrukte, um das Gleiche zu sagen.

  2. Wenn Sie eine Klasse haben, die eine Struktur umschließt, rufen Sie eine Methode in der Klasse auf, um das Mitglied Struktur zu ändern. Das würde ich als gute Programmiergewohnheit tun.

Eine ordnungsgemäße Umsetzung würde folgendermaßen aussehen.

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 ob es ein Problem mit der Programmiergewohnheit und nicht mit struct selbst ist. Strukturen sollen veränderbar 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 . . .

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