Wie erstelle ich eine unveränderliche Klasse?

113

Ich arbeite daran, eine unveränderliche Klasse zu schaffen.
Ich habe alle Eigenschaften als schreibgeschützt markiert.

Ich habe eine Liste von Elementen in der Klasse.
Wenn die Eigenschaft schreibgeschützt ist, kann die Liste geändert werden.

Das Anzeigen der IEnumerable der Liste macht sie unveränderlich.
Ich wollte wissen, nach welchen Grundregeln man eine Klasse unveränderlich machen muss.

Biswanath
quelle
2
Ich empfehle Ihnen dringend, die Blogserie von Eric Lippert über Unveränderlichkeit zu lesen , insbesondere den Eintrag über "Arten von Unveränderlichkeit" . Ihr Kommentar, dass "das Aufdecken der IEnumerable der Liste sie unveränderlich macht", scheint mir etwas seltsam. Was meinst du damit?
Jon Skeet
Was ich damit meinte, war, anstatt zuzulassen, auf die Liste zuzugreifen (wenn das Objekt eine Liste einiger anderer Objekte enthält), dem Benutzer den Zugriff auf die Mitglieder mit IEnumerable zu ermöglichen. Gesprächsliste hier wie in einem bestimmten Beispiel, aber es kann jede Datenstruktur sein.
Biswanath
1
Jon Skeets Links zu Eric Lipperts Blogserie sind unterbrochen, als MSDN diese Blogs archivierte. Bitte siehe „Arten von Unveränderlichkeit“ . Der Rest der Serie scheint im linken Bereich unter Dezember 2007 + Januar 2008 zu liegen.
John Doe
Bitte beachten Sie Eric Lippert Blog - Serie, wie die Menschen verwechseln häufig die Begriffe atomicity, volatilityund immutability: Erster Teil , zweiter Teil , und Teil drei . Diese stammen aus seinem persönlichen Blog und sind meiner Meinung nach für Neulinge freundlicher als seine MSDN-Beiträge.
John Doe

Antworten:

121

Ich denke du bist auf dem richtigen Weg -

  • Alle in die Klasse eingefügten Informationen sollten im Konstruktor bereitgestellt werden
  • Alle Eigenschaften sollten nur Getter sein
  • Wenn eine Sammlung (oder ein Array) an den Konstruktor übergeben wird, sollte sie kopiert werden, damit der Aufrufer sie später nicht mehr ändert
  • Wenn Sie Ihre Sammlung zurückgeben möchten, geben Sie entweder eine Kopie oder eine schreibgeschützte Version zurück (z. B. mit ArrayList.ReadOnly oder ähnlichem). Sie können dies mit dem vorherigen Punkt kombinieren und eine schreibgeschützte Kopie speichern , die zurückgegeben werden soll, wenn Aufrufer greifen darauf zu), geben einen Enumerator zurück oder verwenden eine andere Methode / Eigenschaft, die einen schreibgeschützten Zugriff auf die Sammlung ermöglicht
  • Denken Sie daran, dass Sie möglicherweise immer noch das Aussehen einer veränderlichen Klasse haben, wenn eines Ihrer Mitglieder veränderlich ist. In diesem Fall sollten Sie den Status, den Sie beibehalten möchten, wegkopieren und die Rückgabe ganzer veränderlicher Objekte vermeiden, es sei denn, Sie kopieren sie Bevor Sie sie an den Anrufer zurückgeben - eine andere Möglichkeit besteht darin, nur unveränderliche "Abschnitte" des veränderlichen Objekts zurückzugeben -, danke an @Brian Rasmussen, der mich ermutigt hat, diesen Punkt zu erweitern
Blair Conrad
quelle
Soll ich eine Wrapper-Lookup-Eigenschaft schreiben, mit der Sie nur nachschlagen können? Und danke für die Antwort.
Biswanath
5
Jeder veränderbare Referenztyp, der als Argument an den Konstruktor übergeben wird, sollte kopiert werden. Andernfalls enthält der Anrufer weiterhin einen Verweis auf den Status.
Brian Rasmussen
@Brian Rasmussen, Sie haben Recht, aber selbst wenn es kopiert wird, kann jeder Anrufer auf das veränderbare Objekt zugreifen, abhängig von den bereitgestellten Zugangsdaten. Wenn ein veränderliches Objekt übergeben wird, ist es am besten, wenn die Klasse immer eine andere Kopie oder unveränderliche Abschnitte des Objekts
Blair Conrad
@Blair - Wenn ich ein Wörterbuch in der Klasse habe, das ich nur zum Nachschlagen verwenden möchte. Ist eine schreibgeschützte Eigenschaft, die nachgeschlagen wird, in Ordnung?
Biswanath
@Biswanath - ja, mit der Einschränkung, dass wenn die Werte im Wörterbuch veränderlich sind, Sie sie vor der Rückkehr schützen müssen, wenn Sie nicht möchten, dass sie mutiert werden
Blair Conrad
18

Um unveränderlich zu sein, sollten alle Ihre Eigenschaften und Felder schreibgeschützt sein. Und die Elemente in jeder Liste sollten selbst unveränderlich sein.

Sie können eine schreibgeschützte Listeneigenschaft wie folgt erstellen:

public class MyClass
{
    public MyClass(..., IList<MyType> items)
    {
        ...
        _myReadOnlyList = new List<MyType>(items).AsReadOnly();
    }

    public IList<MyType> MyReadOnlyList
    {
        get { return _myReadOnlyList; }
    }
    private IList<MyType> _myReadOnlyList

}
Joe
quelle
1
Ich denke, ImmutableList wäre besser.
Kevin Wong
Verwenden Sie ImmutableList, und ziehen Sie auch in Betracht, die Klasse zu
versiegeln
Ich bin nicht davon überzeugt, dass ImmutableList gut zu diesem Anwendungsfall passt : basic rules to make a class (that contains a list) immutable. Die Semantik von ImmutableList ermöglicht eine effizientere Speichernutzung beim Hinzufügen von Elementen zu einer Kopie der Liste. Dies scheint mir jedoch ein spezifischerer Anwendungsfall zu sein.
Joe
11

Beachten Sie außerdem Folgendes:

public readonly object[] MyObjects;

ist nicht unveränderlich, auch wenn es mit einem schreibgeschützten Schlüsselwort markiert ist. Sie können einzelne Array-Referenzen / -Werte weiterhin über den Index-Accessor ändern.

lubos hasko
quelle
5

Verwenden Sie die ReadOnlyCollectionKlasse. Es befindet sich im System.Collections.ObjectModelNamespace.

Legen Sie für alles, was Ihre Liste zurückgibt (oder im Konstruktor), die Liste als schreibgeschützte Sammlung fest.

using System.Collections.ObjectModel;

...

public MyClass(..., List<ListItemType> theList, ...)
{
    ...
    this.myListItemCollection= theList.AsReadOnly();
    ...
}

public ReadOnlyCollection<ListItemType> ListItems
{
     get { return this.myListItemCollection; }
}
mbillard
quelle
1

Eine andere Möglichkeit wäre, ein Besuchermuster zu verwenden, anstatt überhaupt interne Sammlungen freizulegen.

Andrew
quelle
1

Durch die Verwendung von ReadOnlyCollection kann der Client diese nicht ändern.

Imad
quelle