So erkennen wir alle die Vorteile von unveränderlichen Typen, insbesondere in Multithread-Szenarien. (Oder zumindest sollten wir alle das realisieren; siehe zum Beispiel System.String.)Unveränderliche Klasse Konstruktion Design
Allerdings, was ich nicht gesehen habe, ist viel Diskussion für Erstellen von unveränderlichen Instanzen, insbesondere Design-Richtlinien.
Angenommen, wir folgende unveränderliche Klasse haben wollen:
class ParagraphStyle {
public TextAlignment Alignment {get;}
public float FirstLineHeadIndent {get;}
// ...
}
Der häufigste Ansatz, den ich gesehen habe, ist wandelbar/unveränderlich „Paare“ von Typen zu haben, z.B. die veränderbaren List<T> und unveränderlichen ReadOnlyCollection<T> Arten oder die veränderbaren StringBuilder und unveränderlichen String Arten.
dieses bestehende Muster imitieren würde die Einführung einer Art „wandelbar“ ParagraphStyle
Typ, der „Duplikate“ die Mitglieder (um Etter), und geben Sie dann einen ParagraphStyle
Konstruktor erfordern, die den wandelbaren Typ als Argument akzeptiert
Also, das funktioniert, unterstützt die Code-Vervollständigung innerhalb der IDE und macht Dinge ziemlich offensichtlich, wie man Dinge konstruiert ... aber es scheint ziemlich duplicative.
Gibt es einen besseren Weg?
Zum Beispiel C# anonyme Typen sind unveränderlich, und lassen Sie „normal“ Eigenschaft Setter für die Initialisierung mit:
var anonymousTypeInstance = new {
Foo = "string",
Bar = 42;
};
anonymousTypeInstance.Foo = "another-value"; // compiler error
Leider ist die nächste Möglichkeit, diese Semantik in C# zu duplizieren ist Konstruktorparameter zu verwenden:
// Option 2:
class ParagraphStyle {
public ParagraphStyle (TextAlignment alignment, float firstLineHeadIndent,
/* ... */) {...}
}
Aber das "skaliert" nicht gut; wenn Ihr Typ z. 15 Eigenschaften, ein Konstruktor mit 15 Parametern ist alles andere als freundlich, und die Bereitstellung "nützlicher" Überladungen für alle 15 Eigenschaften ist ein Rezept für einen Albtraum. Ich lehne es ab.
Wenn wir versuchen, anonyme Typen zu imitieren, so scheint es, dass wir „Set-once“ Eigenschaften in dem „unveränderlich“ Typ, und damit die „wandelbar“ Variante Drop verwenden:
// Option 3:
class ParagraphStyle {
bool alignmentSet;
TextAlignment alignment;
public TextAlignment Alignment {
get {return alignment;}
set {
if (alignmentSet) throw new InvalidOperationException();
alignment = value;
alignmentSet = true;
}
}
// ...
}
Das Problem mit das ist, dass es nicht offensichtlich ist, dass Eigenschaften nur einmal gesetzt werden können (der Compiler wird sich sicherlich nicht beschweren), und die Initialisierung ist nicht Thread-sicher. Es wird daher verlockend, eine Commit()
-Methode hinzuzufügen, so dass das Objekt wissen kann, dass der Entwickler die Eigenschaften eingestellt hat (wodurch alle Eigenschaften verursacht werden, die zuvor nicht gesetzt wurden, wenn ihr Setter aufgerufen wird), aber dies scheint a zu sein Weg, die Dinge noch schlimmer zu machen, nicht besser.
Gibt es ein besseres Design als die veränderbare/unveränderliche Klassenaufteilung? Oder bin ich dazu verdammt, mit der Mitgliederverdopplung fertig zu werden?
Ich weiß, dass dies schwer zu glauben ist, aber ich arbeite mit einer bestehenden C# -Code-Basis. Die Portierung der gesamten Sache auf F # ist keine Option, und selbst wenn es eine Option wäre, scheint die MSR-SSLA-Lizenz, unter der die F # -Bibliotheken veröffentlicht werden, für meine Verwendung nicht geeignet, insbesondere (ii) (c). – jonp
Dies scheint von: http://stackoverflow.com/questions/263585/immutable-object-pattern-in-c-what-do-you-think – jonp
Kann keine Antwort sein, aber ich habe mit ähnlichen Anforderungen behandelt in der Vergangenheit durch Implementierung von ISupportInitialize. Nach einem Aufruf von EndInit() werden Sie unveränderlich und werfen, wenn Setter aufgerufen werden. Nicht Kompilierzeit sicher, aber Dokumentation und werfen früh und oft hilft. – Will