Лучшая практика: как открыть ICollection для чтения

У меня есть ICollection<T>, называемый foos в моем классе, который я хочу открыть как доступный только для чтения (см. этот вопрос). Я вижу, что интерфейс определяет свойство .IsReadOnly, что кажется подходящим... Мой вопрос заключается в следующем: как сделать очевидным для потребителя класса, что foos доступен только для чтения?

Я не хочу полагаться на них, не забывая запросить .IsReadOnly перед тем, как попробовать не реализованный метод, например .Add(). В идеале я хотел бы показать foos как ReadOnlyCollection<T>, но он не реализует IList<T>. Должен ли я раскрывать foo с помощью метода, называемого, например, GetReadOnlyFooCollection, а не через свойство? Если это так, разве это не смущает кого-то, кто тогда ожидает ReadOnlyCollection<T>?

Это С# 2.0, поэтому методы расширения, такие как ToList(), недоступны...

Ответ 1

Кажется, я решил вернуть IEnumerable с клонированными объектами.

public IEnumerable<Foose> GetFooseList() {
   foreach(var foos in Collection) {
     yield return foos.Clone();
   }
}
  • требуется метод Clone на Foos.

Это не дает никаких изменений в коллекции. Помните, что ReadonlyCollection "протекает", поскольку объекты внутри него могут быть изменены, как указано в ссылке в другом сообщении.

Ответ 2

Вы можете сделать "foos" ReadOnlyCollection следующим образом:

ReadOnlyCollection<T> readOnlyCollection = foos.ToList<T>().AsReadOnly();

Затем вы можете открыть его как свойство своего класса.

EDIT:

    class FooContainer
    {
        private ICollection<Foo> foos;
        public ReadOnlyCollection<Foo> ReadOnlyFoos { get { return foos.ToList<Foo>().AsReadOnly();} }

    }

Примечание. Вы должны помнить, что после того, как вы получите коллекцию ReadOnlyFoos, больше не "синхронизируется" с вашим ICos-выбором foos. См. Раздел на который вы ссылаетесь.

Ответ 3

Моя рекомендация - возвращать использование ReadOnlyCollection <T> для сценария напрямую. Это делает использование явным для вызывающего пользователя.

Обычно я предлагаю использовать соответствующий интерфейс. Но, учитывая, что в .NET Framework в настоящее время нет подходящего IReadOnlyCollection, вы должны перейти к типу ReadOnlyCollection.

Также вы должны знать, когда используете ReadOnlyCollection, потому что он фактически не доступен только для чтения: Неизменяемость и ReadOnlyCollection

Ответ 4

Поскольку вопрос был написан,.NET 4.0 добавил интерфейс IReadOnlyCollection<T>; вероятно, было бы полезно использовать это как объявленный тип возврата.

Это, однако, оставляет открытым вопрос о том, какой тип экземпляра должен возвращаться. Один из подходов - клонировать все элементы в исходной коллекции. Другим было бы всегда возвращать обертку только для чтения. Третьим было бы вернуть исходную коллекцию, если она реализует IReadOnlyCollection<T>. Каждый подход будет лучшим в определенных контекстах, но будет менее идеальным (или, может быть, просто ужасным) в других. К сожалению, Microsoft не предоставляет стандартных средств, с помощью которых можно задать два вопроса:

  • Вы обещаете всегда и навсегда содержать те же предметы, что и вы сейчас?

  • Можете ли вы безопасно подвергать себя воздействию кода, который не должен изменять ваше содержимое.

Какой стиль упаковки подходит, будет зависеть от того, что клиентский код ожидает делать с тем, что он получает. Некоторые сценарии, которые следует избегать:

  • Объект был предоставлен с типом, который клиент будет распознавать как неизменяемый, но вместо того, чтобы возвращаться непосредственно, он дублируется, используя тип, который клиент не признает неизменным. Следовательно, клиент вынужден снова дублировать сбор.

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

  • Объект изменчивого типа, который не должен быть мутирован, предоставляется клиентом (перевод его в интерфейс только для чтения). Затем он подвергается прямо другому клиенту, который определяет, что он является изменяемым типом и продолжает его изменять.

  • Получается ссылка на изменяемую коллекцию и инкапсулируется в оболочку только для чтения, а затем возвращается клиенту, которому требуется неизменяемый объект. Метод, который возвратил коллекцию, обещал, что он является неизменным, и, таким образом, клиент отказался сделать свою собственную защитную копию. Затем клиент плохо подготовлен к возможности изменения коллекции.

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

Я хочу, чтобы Microsoft добавила стандартные средства, с помощью которых коллекциям можно было бы задавать вышеуказанные вопросы или, еще лучше, было предложено создать неизменный моментальный снимок их текущего состояния. Непревзойденная коллекция может вернуть неизменяемый снимок своего текущего состояния очень дешево (просто вернуть себя), и даже некоторые изменчивые типы коллекций могут вернуть неизменный моментальный снимок по цене, намного ниже стоимости полного перечисления (например, можно использовать List<T> двумя массивами T[][], один из которых содержит ссылки на разделяемые неизменяемые массивы из 256 элементов, а другой содержит ссылки на неустойчивые изменяемые массивы из 256 элементов. Создание моментального снимка списка потребует клонирования только внутренних массивов, содержащих элементы которые были изменены с момента последнего моментального снимка, что потенциально намного дешевле, чем клонирование всего списка. К сожалению, поскольку нет стандартного интерфейса "сделать неизменный снимок" [обратите внимание, что ICloneable не учитывается, так как клон измененного списка будет изменяться, тогда как можно сделать неизменный моментальный снимок, инкапсулируя измененный клон в оболочку только для чтения, которая будет работать только для вещей, которые являются клонируемыми, и даже типы, которые не являются клонируемыми, должны быть плохо поддерживают функцию "изменяемого моментального снимка".]

Ответ 5

Иногда вам может понадобиться использовать интерфейс, возможно, потому, что вы хотите издеваться над коллекцией во время модульного тестирования. См. Мою запись в блоге для добавления собственного интерфейса к ReadonlyCollection с помощью адаптера.

Ответ 6

Я обычно возвращаю IEnumerable<T>.

Как только вы сделаете сборку readonly (так что методы, такие как Add, Remove и Clear больше не работают), осталось мало того, что коллекция поддерживает то, что перечислимое не имеет - просто Count и Contains, Я считаю.

Если потребителям вашего класса действительно нужно обращаться с такими элементами, как они есть в коллекции, достаточно легко передать конструктор IEnumerable в List<T>.

Ответ 7

Вернуть T []:

private ICollection<T> items;

public T[] Items
{
    get { return new List<T>(items).ToArray(); }
}