Jak utworzyć niezmienną klasę?

113

Pracuję nad stworzeniem niezmiennej klasy.
Oznaczyłem wszystkie właściwości jako tylko do odczytu.

Mam listę przedmiotów w klasie.
Chociaż jeśli właściwość jest tylko do odczytu, lista może być modyfikowana.

Ujawnienie IEnumerable listy sprawia, że ​​jest ona niezmienna.
Chciałem wiedzieć, jakie są podstawowe zasady, których należy przestrzegać, aby klasa była niezmienna?

Biswanath
źródło
2
Gorąco polecam przeczytanie serii blogów Erica Lipperta na temat niezmienności , w szczególności wpisu o „rodzajach niezmienności” . Twój komentarz, że „Ujawnienie IEnumerable listy sprawia, że ​​jest on niezmienny” wydaje mi się nieco dziwny. Co przez to rozumiesz?
Jon Skeet
Chodziło mi o to, aby zamiast zezwalać na dostęp do listy (jeśli obiekt ma listę innych obiektów), zezwolenie użytkownikowi na dostęp do członków za pomocą IEnumerable. Lista do omówienia tutaj, jak w konkretnym przykładzie, ale może to być dowolna struktura danych.
Biswanath
1
Linki Jona Skeeta do serii blogów Erica Lipperta zepsuły się, gdy MSDN archiwizowało te blogi. Zobacz „rodzaje niezmienności” . Reszta serii wydaje się być mniej niż grudzień 2007 + styczeń 2008 w lewym panelu.
John Doe
Proszę zobaczyć na blogu serię Eric Lippert na temat tego jak ludzie często mylą te warunki atomicity, volatilityoraz immutability: część pierwsza , część druga , a Part Three . Pochodzą one z jego osobistego bloga i, jak sądzę, są bardziej przyjazne dla początkujących niż jego posty w witrynie MSDN.
John Doe

Odpowiedzi:

121

Myślę, że jesteś na dobrej drodze -

  • wszystkie informacje wprowadzone do klasy powinny być dostarczone w konstruktorze
  • wszystkie właściwości powinny być tylko getterami
  • jeśli kolekcja (lub Array) jest przekazywana do konstruktora, należy ją skopiować, aby wywołujący nie mógł później jej modyfikować
  • jeśli zamierzasz zwrócić swoją kolekcję, zwróć kopię lub wersję tylko do odczytu (na przykład za pomocą ArrayList.ReadOnly lub podobnego - możesz połączyć to z poprzednim punktem i zapisać kopię tylko do odczytu, która zostanie zwrócona, gdy wywołujący mają do niego dostęp), zwracają moduł wyliczający lub używają innej metody / właściwości, która umożliwia dostęp tylko do odczytu do kolekcji
  • pamiętaj, że nadal możesz wyglądać jak zmienna klasa, jeśli którykolwiek z twoich członków jest zmienny - w takim przypadku powinieneś skopiować dowolny stan, który chcesz zachować i unikać zwracania całych zmiennych obiektów, chyba że je skopiujesz przed oddaniem ich z powrotem dzwoniącemu - inną opcją jest zwrócenie tylko niezmiennych "sekcji" zmiennego obiektu - podziękowania dla @Brian Rasmussen za zachęcenie mnie do rozwinięcia tego punktu
Blair Conrad
źródło
Czy powinienem napisać właściwość wyszukiwania opakowania, która pozwala tylko na wyszukiwanie? Dziękuję za odpowiedź.
Biswanath
5
Każdy zmienny typ odwołania przekazany jako argument do konstruktora powinien zostać skopiowany. W przeciwnym razie dzwoniący nadal będzie posiadał odniesienie do stanu.
Brian Rasmussen
@Brian Rasmussen, masz rację, ale nawet jeśli jest skopiowany, każdy wywołujący może uzyskać dostęp do obiektu mutowalnego, w zależności od dostarczonych acces.sors. W przypadku przekazania obiektu zmiennego klasy najlepiej jest zawsze zwracać inną kopię lub niezmienne sekcje obiektu
Blair Conrad
@Blair - Jeśli mam w klasie słownik, którego chcę użyć do wyszukiwania. Czy właściwość tylko do odczytu, która wykonuje wyszukiwanie, powinna być w porządku?
Biswanath
@Biswanath - tak, z zastrzeżeniem, że jeśli wartości w słowniku są zmienne, to będziesz musiał je zabezpieczyć przed powrotem, jeśli nie chcesz, aby były zmutowane
Blair Conrad
18

Aby były niezmienne, wszystkie właściwości i pola powinny być tylko do odczytu. A pozycje na każdej liście powinny same być niezmienne.

Możesz utworzyć właściwość listy tylko do odczytu w następujący sposób:

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
źródło
1
Myślę, że ImmutableList byłoby lepsze.
Kevin Wong
użyj ImmutableList, rozważ również zapieczętowanie klasy
kofifus
Nie jestem przekonany, że ImmutableList jest dobrym rozwiązaniem w tym przypadku użycia: basic rules to make a class (that contains a list) immutable. Semantyka ImmutableList umożliwia wydajniejsze wykorzystanie pamięci podczas dodawania elementów do kopii listy, ale wydaje mi się, że jest to bardziej szczegółowy przypadek użycia.
Joe
11

Pamiętaj też, że:

public readonly object[] MyObjects;

nie jest niezmienna, nawet jeśli jest oznaczona słowem kluczowym readonly. Nadal można zmieniać odwołania / wartości do poszczególnych tablic według metody dostępu indeksu.

lubos hasko
źródło
5

Skorzystaj z ReadOnlyCollectionklasy. Znajduje się w System.Collections.ObjectModelprzestrzeni nazw.

W przypadku wszystkiego, co zwraca twoją listę (lub w konstruktorze), ustaw listę jako kolekcję tylko do odczytu.

using System.Collections.ObjectModel;

...

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

public ReadOnlyCollection<ListItemType> ListItems
{
     get { return this.myListItemCollection; }
}
mbillard
źródło
1

Inną opcją byłoby użycie wzorca odwiedzających zamiast ujawniania jakichkolwiek wewnętrznych kolekcji.

Andrzej
źródło
1

Użycie ReadOnlyCollection ograniczy klientowi możliwość jego modyfikowania.

Imad
źródło