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?


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

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

}

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.


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; }
}

1

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


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.