Лучшая практика: как выставить ICollection только для чтения - PullRequest
10 голосов
/ 12 ноября 2008

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

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

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

Ответы [ 7 ]

15 голосов
/ 13 ноября 2008

Вы можете сделать «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 больше не "синхронизируется" с вашей коллекцией foos ICollection. См. Ветку , на которую вы ссылались .

3 голосов
/ 20 сентября 2014

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

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

  1. Обещаете ли вы всегда и навсегда содержать те же предметы, что и сейчас?

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

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

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

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

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

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

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

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

3 голосов
/ 13 ноября 2008

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

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

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

0 голосов
/ 30 июля 2009

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

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

Это не позволяет вносить изменения в коллекцию. Помните, что ReadonlyCollection является «негерметичным», поскольку объекты внутри него могут быть изменены, как указано в ссылке в другом посте.

0 голосов
/ 11 февраля 2009

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

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

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

0 голосов
/ 11 февраля 2009

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

0 голосов
/ 13 ноября 2008

Вернуть T []:

private ICollection<T> items;

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