Как создать неизменяемый класс?


113

Я работаю над созданием неизменяемого класса.
Я пометил все свойства как доступные только для чтения.

У меня есть список предметов в классе.
Хотя, если свойство доступно только для чтения, список можно изменить.

Открытие IEnumerable списка делает его неизменяемым.
Я хотел знать, каковы основные правила, которым нужно следовать, чтобы сделать класс неизменяемым?


2
Я настоятельно рекомендую вам прочитать серию блогов Эрика Липперта о неизменности , в частности статью о «видах неизменяемости» . Ваш комментарий о том, что «предоставление IEnumerable списка делает его неизменным» кажется мне несколько странным. Что вы имеете в виду?
Джон Скит,

Я имел в виду, что вместо разрешения доступа к списку (если у объекта есть список некоторых других объектов) пользователь может получить доступ к членам с помощью IEnumerable. Обсуждение списка здесь как в конкретном примере, но это может быть любая структура данных.
Бисванат

1
Ссылки Джона Скита на серию блогов Эрика Липперта не работают, когда MSDN заархивировала эти блоги. См. «Виды неизменности» . Остальная часть серии, кажется, находится под декабрь 2007 г. + январь 2008 г. на левой панели.
Джон Доу

Пожалуйста , смотрите серию блога Эрика Липперта о том , как люди обычно путают термины atomicity, volatilityи immutability: Часть первая , часть вторая , и часть третья . Это из его личного блога, и я считаю, что они более удобны для новичков, чем его сообщения в MSDN.
Джон Доу

Ответы:


121

Я думаю, ты на правильном пути -

  • вся информация, вводимая в класс, должна передаваться в конструктор
  • все свойства должны быть только геттерами
  • если в конструктор передается коллекция (или массив), ее следует скопировать, чтобы вызывающая сторона не могла изменить ее позже
  • если вы собираетесь вернуть свою коллекцию, либо верните копию, либо версию только для чтения (например, используя ArrayList.ReadOnly или аналогичный - вы можете объединить это с предыдущей точкой и сохранить копию только для чтения, которая будет возвращена, когда вызывающие абоненты получают к нему доступ), возвращают перечислитель или используют какой-либо другой метод / свойство, которое разрешает доступ только для чтения к коллекции
  • имейте в виду, что у вас все еще может быть вид изменяемого класса, если какой-либо из ваших членов является изменяемым - в этом случае вы должны скопировать любое состояние, которое вы хотите сохранить, и избегать возврата целых изменяемых объектов, если вы не скопируете их перед тем, как вернуть их вызывающему - другой вариант - вернуть только неизменяемые «разделы» изменяемого объекта - спасибо @Brian Rasmussen за то, что побудил меня расширить этот пункт

Должен ли я написать свойство поиска оболочки, которое позволяет выполнять только поиск? И спасибо за ответ.
Бисванат,

5
Любой изменяемый ссылочный тип, переданный в качестве аргумента конструктору, должен быть скопирован. В противном случае вызывающий абонент по-прежнему будет содержать ссылку на состояние.
Брайан Расмуссен,

@Brian Rasmussen, вы правы, но даже если он скопирован, любой вызывающий может получить доступ к изменяемому объекту, в зависимости от предоставленного acces.sors. В случае передачи изменяемого объекта классу лучше всегда возвращать другую копию или неизменяемые части объекта,
Блэр Конрад,

@Blair - Если у меня есть словарь в классе, я просто хочу использовать его для поиска. Разве свойство только для чтения, которое выполняет поиск, должно быть в порядке?
Бисванат

@Biswanath - да, с оговоркой, что если значения в словаре изменчивы, вам нужно будет защитить их перед возвратом, если вы не хотите, чтобы они мутировали
Блэр Конрад

18

Чтобы быть неизменными, все ваши свойства и поля должны быть доступны только для чтения. И элементы в любом списке должны быть неизменными.

Вы можете сделать свойство списка только для чтения следующим образом:

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
Я думаю, что ImmutableList было бы лучше.
Кевин Вонг

используйте ImmutableList, также подумайте о том, чтобы запечатать класс
кофифус

Я не уверен , что ImmutableList хорошо подходит для этого случая использования: basic rules to make a class (that contains a list) immutable. Семантика ImmutableList позволяет более эффективно использовать память при добавлении элементов в копию списка, но мне кажется, что это более конкретный вариант использования.
Джо

11

Также имейте в виду, что:

public readonly object[] MyObjects;

не является неизменным, даже если он помечен ключевым словом readonly. Вы по-прежнему можете изменять ссылки / значения отдельных массивов с помощью средства доступа к индексу.


5

Воспользуйтесь ReadOnlyCollectionклассом. Он находится в System.Collections.ObjectModelпространстве имен.

Для всего, что возвращает ваш список (или в конструкторе), установите список как доступную только для чтения коллекцию.

using System.Collections.ObjectModel;

...

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

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

1

Другой вариант - использовать шаблон посетителя вместо того, чтобы вообще открывать какие-либо внутренние коллекции.


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.