Как предотвратить изменение возвращаемой коллекции вызывающим методом? [дубликат]


На этот вопрос уже есть ответ здесь:

У меня есть методы, возвращающие частные коллекции вызывающему объекту, и я хочу запретить вызывающему объекту изменять возвращенные коллекции.

private readonly Foo[] foos;

public IEnumerable<Foo> GetFoos()
{
    return this.foos;
}

На данный момент частная коллекция представляет собой фиксированный массив, но в будущем коллекция может превратиться в список, если возникнет необходимость добавления новых элементов во время выполнения.

Существует несколько способов предотвратить изменение коллекции вызывающей стороной. Возврат IEnumerable<T> является самым простым решением, но вызывающий объект все равно может привести возвращаемое значение к IList<T> и изменить коллекцию.

((IList<Foo>)GetFoos())[0] = otherFoo;
Клонирование коллекций имеет очевидный недостаток, заключающийся в том, что существуют две коллекции, которые могут развиваться независимо. До сих пор я рассматривал следующие возможности.
  1. упаковка коллекции в ReadOnlyCollection<T>.
  2. возвращает один из итераторов LINQ, определенных классом Enumerable, выполняя фиктивную проекцию типа list.Select(item => item). На самом деле я рассматриваю использование Where(item => true), потому что возвращаемый итератор кажется более легким.
  3. написание пользовательской оболочки.

Что мне не нравится в использовании ReadOnlyCollection<T>, так это то, что он реализует IList<T> и вызов Add() или доступ к индексатору вызовет исключения. Хотя это абсолютно правильно в теории, почти нет реальных проверок кода IList<T>.IsReadOnly или IList<T>.IsFixedSize.

Использование итераторов LINQ-я обернул код в метод расширения MakeReadOnly() - предотвращает этот сценарий, но он имеет вкус взлома.

Написание пользовательской оболочки? Изобретать велосипед заново?

Какие-либо мысли, соображения или другие решения?


Помечая этот вопрос, я обнаружил этот вопрос переполнения стека, который я раньше не замечал. Джон Скит предлагает использовать " LINQ hack", тоже, но еще более эффективное использование Skip(0).

4 3

4 ответа:

К сожалению, нет способа достичьточно того, что вы ищете в текущей версии фреймворка. Он просто не имеет понятия индексируемой неизменяемой / доступной только для чтения коллекции как по конкретному типу, так и по стилю интерфейса.

Как вы указали, ReadOnlyCollection<T> работает нормально на стороне конкретного типа. Но нет соответствующего интерфейса для реализации, который также статически доступен только для чтения.

У тебя есть только один реальный выбор ...

  • определите свой собственный класс сборник
  • либо только реализовать IEnumerable<T>, либо определить необходимый интерфейс только для чтения, который реализует ваша коллекция.

Как насчет создания глубокой копии возвращаемого объекта? [Таким образом, не будет никакого эффекта на исходную коллекцию, даже если вызывающий решит внести изменения в возвращенную копию]

Список и массив имеют метод AsReadOnly:

public IEnumerable<Foo> GetFoos()
{
    return Array.AsReadOnly(this.foos);
    // or if it is a List<T>
    // return this.foos.AsReadOnly();
}

В таких случаях я создаю методы в классе для доступа к отдельным элементам частной коллекции. Вы также можете реализовать индексатор (http://msdn.microsoft.com/en-us/library/6x16t2tx.aspx ) о классе, содержащем частную коллекцию.