Как запретить вызывающему методу изменять возвращаемую коллекцию?[дубликат]
-
10-07-2019 - |
Вопрос
На этот вопрос уже есть ответ здесь:
У меня есть методы, возвращающие частные коллекции вызывающему объекту, и я хочу запретить вызывающему объекту изменять возвращаемые коллекции.
private readonly Foo[] foos;
public IEnumerable<Foo> GetFoos()
{
return this.foos;
}
На данный момент частная коллекция представляет собой фиксированный массив, но в будущем коллекция может стать списком, если возникнет необходимость добавления новых элементов во время выполнения.
Существует несколько решений, позволяющих запретить модификацию коллекции вызывающей стороной.Возвращение IEnumerable<T>
является самым простым решением, но вызывающая сторона все равно может преобразовать возвращаемое значение в IList<T>
и изменить коллекцию.
((IList<Foo>)GetFoos())[0] = otherFoo;
Клонирование коллекций имеет очевидный недостаток: есть две коллекции, которые могут развиваться независимо.Пока я рассмотрел следующие варианты.
- Упаковываем коллекцию в
ReadOnlyCollection<T>
. - Возврат одного из итераторов LINQ, определенных
Enumerable
класс, выполнив фиктивную проекцию, напримерlist.Select(item => item)
.На самом деле я рассматриваю возможность использованияWhere(item => true)
потому что возвращенный итератор кажется более легким. - Написание пользовательской оболочки.
Что мне не нравится в использовании ReadOnlyCollection<T>
заключается в том, что он реализует IList<T>
и звоню Add()
или доступ к индексатору вызовет исключения.Хотя в теории это абсолютно правильно, реальных проверок кода почти нет. IList<T>.IsReadOnly
или IList<T>.IsFixedSize
.
Использование итераторов LINQ. Я обернул код в метод расширения. MakeReadOnly()
- предотвращает этот сценарий, но имеет привкус взлома.
Написание пользовательской оболочки?Изобретать велосипед заново?
Есть какие-нибудь мысли, соображения или другие решения?
Отмечая этот вопрос, я обнаружил этот вопрос о переполнении стека Я раньше не замечал.Джон Скит также предлагает использовать «хак LINQ», но еще эффективнее использовать Skip(0)
.
Решение
К сожалению, нет способа достичь точно то, что вы ищете в текущей версии фреймворка.Он просто не имеет понятия индексируемой неизменяемой/доступной только для чтения коллекции как для конкретного типа, так и для стиля интерфейса.
Как вы отметили, ReadOnlyCollection<T>
работает нормально на стороне конкретного типа.Но не существует соответствующего интерфейса для реализации, который также статически доступен только для чтения.
Ваш единственный реальный выбор - это...
- Определите свой собственный класс коллекции
- Либо только реализовать
IEnumerable<T>
или определите необходимый интерфейс только для чтения, который реализует ваша коллекция.
Другие советы
Как насчет создания глубокой копии возвращаемого объекта?[Поэтому исходная коллекция не окажет никакого влияния, даже если вызывающая сторона решит внести изменения в возвращаемую копию]
List и Array имеют метод 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) в классе, содержащем частную коллекцию.