Возврат «IList» против «ICollection» против «Collection»

120

Я не понимаю, какой тип коллекции я должен возвращать из моих общедоступных методов и свойств API.

Я имею в виду коллекции IList, ICollectionи Collection.

Всегда ли предпочтительнее возвращать один из этих типов по сравнению с другими или это зависит от конкретной ситуации?

Рокки Сингх
источник
4
дубликат: stackoverflow.com/questions/271710/…
Робби,
также: stackoverflow.com/questions/4455428/…
nathanchere

Ответы:

71

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

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

Guffa
источник
25
Но также рассмотрите ICollection <T>, который расширяет IEnumerable <T> для предоставления дополнительных утилит, включая свойство Count. Это может быть полезно, потому что вы можете определить длину последовательности, не просматривая последовательность несколько раз.
retrodrone 06
11
@retrodrone: на самом деле реализация Countметода проверяет фактический тип коллекции, поэтому он будет использовать свойства Lengthили Countдля некоторых известных типов, таких как массивы, и ICollectionдаже когда вы предоставляете его как IEnumerable.
Guffa
8
@Guffa: Возможно, стоит отметить, что если класс Thing<T>реализует, IList<T>но не неуниверсальный ICollection, тогда вызов IEnumerable<Cat>.Count()a Thing<Cat>будет быстрым, но вызов IEnumerable<Animal>.Count()будет медленным (поскольку метод расширения будет искать, а не находить, реализацию ICollection<Cat>). Однако, если класс реализует неуниверсальный ICollection, IEnumerable<Animal>.Countон найдет и использует это.
supercat
8
Я не согласен. Лучше всего вернуть самый богатый тип, который у вас есть, чтобы клиент мог использовать его напрямую, если он этого хочет. Если у вас есть List <>, зачем возвращать IEnuerable <> только для того, чтобы вызывающий абонент без надобности вызвал ToList ()?
zumalifeguard
140

ICollection<T>представляет собой интерфейс , который предоставляет коллекцию семантики , такие как Add(), Remove(), и Count.

Collection<T>это конкретная реализация ICollection<T>интерфейса.

IList<T>по сути, имеет ICollection<T>доступ в произвольном порядке.

В этом случае вы должны решить, требуется ли для ваших результатов семантика списка, такая как индексирование на основе порядка (затем используйте IList<T>), или вам просто нужно вернуть неупорядоченный «мешок» результатов (затем использовать ICollection<T>).

cordialgerm
источник
5
Collection<T>орудия IList<T>и не только ICollection<T>.
CodesInChaos,
56
Все коллекции в .Net заказаны, потому что IEnumerable<T>заказаны. Что отличает IList<T>от, так ICollection<T>это то, что он предлагает участникам работать с индексами. Например, list[5]работает, но collection[5]не компилируется.
svick
5
Я также не согласен с тем, что упорядоченные коллекции должны реализовываться IList<T>. Есть множество заказанных коллекций, которых нет. IList<T>о быстром индексированном доступе.
CodesInChaos,
4
@yoyo Классы и интерфейсы коллекций .net просто плохо спроектированы и плохо названы. Я бы не стал особо задумываться о том, почему они так их назвали.
CodesInChaos 07
6
@svick: Все коллекции заказаны, потому что IEnumerable<T>заказаны? Бери HashSet<T>- реализует IEnumerable<T>но явно не заказано. Т.е. вы не можете повлиять на порядок элементов, и этот порядок может измениться в любое время, если внутренняя хеш-таблица будет реорганизована.
Оливье Жако-Декомб
62

Основное различие между IList<T>и ICollection<T>заключается в том, что IList<T>вы можете получать доступ к элементам через индекс. IList<T>описывает типы, подобные массивам. ICollection<T>Доступ к элементам в объекте можно получить только через перечисление. Оба позволяют вставлять и удалять элементы.

Если вам нужно только перечислить коллекцию, IEnumerable<T>это предпочтительнее. Он имеет два преимущества перед остальными:

  1. Он запрещает вносить изменения в коллекцию (но не в элементы, если они ссылочного типа).

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

Collection<T>- это базовый класс, который в основном полезен разработчикам коллекций. Если вы предоставите его в интерфейсах (API), многие полезные коллекции, не являющиеся производными от него, будут исключены.


Одним из недостатков IList<T>является то, что массивы реализуют это, но не позволяют добавлять или удалять элементы (т.е. вы не можете изменять длину массива). Если вы вызываете IList<T>.Add(item)массив, будет выброшено исключение . Ситуация несколько смягчена, поскольку IList<T>есть логическое свойство, IsReadOnlyкоторое вы можете проверить, прежде чем пытаться это сделать. Но на мой взгляд, это по-прежнему недостаток дизайна библиотеки . Поэтому использую List<T>напрямую, когда требуется возможность добавлять или удалять элементы.

Оливье Жако-Декомб
источник
Анализ кода (и FxCop) советует , что при возврате конкретный общий сбор, один из следующих должны быть возвращены: Collection, ReadOnlyCollectionили KeyedCollection. И это Listне должно быть возвращено.
DavidRR
@DavidRR: Часто бывает полезно передать список методу, который должен добавлять или удалять элементы. Если вы введете параметр как IList<T>, невозможно гарантировать, что метод не будет вызываться с массивом в качестве параметра.
Оливье Жако-Декомб
7

IList<T>является базовым интерфейсом для всех общих списков. Поскольку это упорядоченная коллекция, реализация может выбрать порядок, начиная от отсортированного до порядка вставки. Кроме того, Ilistесть свойство Item, которое позволяет методам читать и редактировать записи в списке на основе их индекса. Это позволяет вставлять, удалять значение в / из списка по индексу позиции.

Кроме того IList<T> : ICollection<T>, ICollection<T>здесь доступны для реализации все методы из .

ICollection<T>является базовым интерфейсом для всех общих коллекций. Он определяет размер, счетчики и методы синхронизации. Вы можете добавить или удалить элемент в коллекцию, но вы не можете выбрать, в какой позиции это происходит из-за отсутствия свойства index.

Collection<T>обеспечивает реализацию IList<T>, IListи IReadOnlyList<T>.

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

Цитата из источника ,

ICollection, ICollection<T>: Вы хотите изменить коллекцию или заботитесь о ее размере. IList, IList<T>: Вы хотите изменить коллекцию и заботитесь о порядке и / или расположении элементов в коллекции.

NullReference
источник
5

Возврат типа интерфейса является более общим, поэтому (не имея дополнительной информации о вашем конкретном варианте использования) я бы склонился к этому. Если вы хотите выставить поддержку индексирования, выберите IList<T>, иначе ICollection<T>будет достаточно. Наконец, если вы хотите указать, что возвращаемые типы доступны только для чтения, выберите IEnumerable<T>.

И, если вы не читали его раньше, Брэд Абрамс и Кшиштоф Квалина написали отличную книгу под названием «Рекомендации по разработке фреймворка: соглашения, идиомы и шаблоны для многоразовых библиотек .NET» (вы можете скачать дайджест отсюда ).

Алан
источник
IEnumerable<T>только чтение? Конечно, но при перенастройке в контексте репозитория даже после выполнения ToList не является потокобезопасным. Проблема в том, что когда исходный источник данных получает GC, IEnumarble.ToList () не работает в многопоточном контексте. Он работает по линейному asyncпринципу, потому что GC вызывается после того, как вы выполняете работу, но использование дней в 4.5+ IEnumarbale становится головной болью.
Петр Кула
-3

Из этого вопроса вытекает несколько тем:

  • интерфейсы против классов
  • какой конкретный класс из нескольких одинаковых классов, коллекции, списка, массива?
  • Общие классы по сравнению с коллекциями подэлементов ("дженериков")

Вы можете выделить, что это объектно-ориентированный API

интерфейсы против классов

Если у вас нет большого опыта работы с интерфейсами, я рекомендую придерживаться классов. Я часто вижу, как разработчики перескакивают на интерфейсы, даже если это не обязательно.

И, наконец, закончите делать плохой дизайн интерфейса вместо хорошего дизайна класса, который, кстати, в конечном итоге можно перенести на хороший дизайн интерфейса ...

Вы увидите много интерфейсов в API, но не торопитесь, если он вам не нужен.

Со временем вы узнаете, как применять интерфейсы к вашему коду.

какой конкретный класс из нескольких одинаковых классов, коллекции, списка, массива?

В C # (dotnet) есть несколько классов, которые можно менять местами. Как уже упоминалось, если вам нужно что-то из более конкретного класса, например «CanBeSortedClass», сделайте это явным образом в своем API.

Действительно ли вашему пользователю API нужно знать, что ваш класс можно отсортировать, или применить какой-то формат к элементам? Затем используйте CanBeSortedClass или ElementsCanBePaintedClass, в противном случае используйте GenericBrandClass.

В противном случае используйте более общий класс.

Общие классы коллекций по сравнению с коллекциями подэлементов ("обобщенных")

Вы обнаружите, что есть классы, содержащие другие элементы, и вы можете указать, что все элементы должны быть определенного типа.

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

Потребуется ли вашему пользователю API очень специфический тип, одинаковый для всех элементов?

Используйте что-нибудь вроде List<WashingtonApple>.

Потребуется ли вашему пользователю API несколько связанных типов?

Expose List<Fruit>для API, и использовать List<Orange> List<Banana>, List<Strawberry>внутри, где Orange, Bananaи Strawberryявляются потомками Fruit.

Потребуется ли вашему пользователю API общая коллекция типов?

Используйте List, где находятся все предметы object.

Приветствия.

umlcat
источник
7
«Если у вас нет большого опыта работы с интерфейсами, я рекомендую придерживаться классов». Это не лучший совет. Это все равно что сказать, что если вы чего-то не знаете, просто держитесь от этого подальше. Интерфейсы чрезвычайно мощны и поддерживают концепцию «Программы для абстракций против конкреций». Они также весьма эффективны для выполнения модульного тестирования из-за способности обмениваться типами с помощью имитаций. Существует множество других веских причин использовать интерфейсы помимо этих комментариев, поэтому, пожалуйста, обязательно изучите их.
atconway
@atconway Я регулярно использую интерфейсы, но, как и многие концепции, это мощный инструмент, который можно легко смешивать. И иногда разработчику это может не понадобиться. Мое предложение не было «никогда не использовать интерфейсы», я предлагал «в этом случае вы можете пропустить интерфейсы. Узнайте об этом, и вы, возможно, получите лучшее из них позже». Приветствия.
umlcat
1
Я рекомендую всегда программировать на интерфейс. Это помогает сохранить слабую связь кода.
mac10688
@ mac10688 Мой предыдущий ответ (atconway) также относится к вашему комментарию.
umlcat