2009-08-02 5 views
3

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

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() - предотвращает этот сценарий, но у него есть вкус взлома.

Написание пользовательской обертки? Переосмыслить колесо?

Любые мысли, соображения или другие решения?


Хотя разметили этот вопрос, я обнаружил this Stack Overflow question я не замечал раньше. Jon Skeet предлагает использовать «LINQ hack» тоже, но еще эффективнее, используя Skip(0).

+1

Если есть проблемы с безопасностью (потенциально враждебные абоненты), я вообще не беспокоюсь о защите от неразумного каста или вызова Add на ReadOnlyCollection. Глупые разработчики также могут использовать функции отражения и доступа. Трудно сделать код идиотом. Целью является защита от невинного использования публичного интерфейса. – TrueWill

ответ

5

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

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

Вы только реальный выбор, чтобы ...

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

Я принял этот ответ, потому что он поясняет, что нет встроенной поддержки. Решено придерживаться Enumerable.Skip (0). –

+0

@ DanielBrückner: Какое реальное преимущество имеет обертывание в 'ReadOnlyCollection ' и литье в' IEnumerable '? Тот факт, что результирующий объект может быть добавлен в 'IList ', значительно улучшит производительность некоторых методов Linq, таких как' Count' и 'Last'. – supercat

0

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

+0

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

0

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

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

Эти методы возвращают коллекцию, завернутую в ReadOnlyCollection , и мне не нравится, что этот класс реализует IList , как описано в вопросе. –

0

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

+0

Реализация метода для получения одного элемента имеет как минимум два недостатка - требуется второй метод или свойство для возврата количества элементов в коллекции, и все преимущества IEnumerable - foreach и LINQ - теряются. Для создания индексатора требуется метод или свойство, возвращающее количество элементов, а некоторые классы имеют несколько методов, возвращающих коллекции. Это исключает индексацию. –

+0

Да, конечно, это зависит от ваших задач. Но вы можете добавить поле, которое возвращает количество элементов в частной коллекции. Вы также можете создать внутренний класс, который реализует логику доступа. – RollingStone