Повернення "IList" vs "ICollection" vs "Collection"


119

Мене бентежить, який тип колекції я повинен повернути зі своїх методів та властивостей публічного API.

Колекції , які я маю на увазі , є IList, ICollectionі Collection.

Чи повернення одного з цих типів завжди віддається перевагу перед іншими, чи це залежить від конкретної ситуації?



Відповіді:


71

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

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


25
Але також врахуйте ICollection <T>, який розширює IEnumerable <T>, щоб забезпечити додаткові утиліти, включаючи властивість Count. Це може бути корисно, тому що ви можете визначити довжину послідовності, не обходячи послідовність кілька разів.
ретродрон

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
Я не погоджуюсь. Краще повернути найбагатший тип, який у вас є, щоб клієнт міг ним користуватися безпосередньо, якщо це те, що він хоче. Якщо у вас є Список <>, навіщо повертати IEnuerable <>, щоб змусити абонента без потреби викликати ToList ()?
zumalifeguard

140

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

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

IList<T>по суті є ICollection<T>випадковим порядком доступу.

У цьому випадку слід вирішити, чи потребують ваші результати семантику списку, таку як індексація на основі замовлення (потім використання IList<T>) чи потрібно просто повернути не упорядкований "мішок" результатів (потім використовувати ICollection<T>).


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

6
@svick: Чи всі колекції замовлені тому IEnumerable<T>, що замовлено? Візьміть HashSet<T>- він реалізує, IEnumerable<T>але явно не замовлений. Тобто у вас немає впливу на порядок позицій, і цей порядок може змінитися в будь-який час, якщо внутрішня хеш-таблиця буде переорганізована.
Олів'є Якот-Дескомб

61

Основна відмінність між 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>є базовим інтерфейсом для всіх загальних колекцій. Він визначає розмір, нумератори та методи синхронізації. Ви можете додати або вилучити елемент у колекцію, але ви не можете вибрати, в якій позиції це відбувається через відсутність властивості індексу.

Collection<T>забезпечує реалізацію IList<T>, IListі IReadOnlyList<T>.

Якщо ви використовуєте більш вузький тип інтерфейсу, наприклад ICollection<T>замість цього IList<T>, ви захищаєте свій код від порушення змін. Якщо ви використовуєте більш широкий тип інтерфейсу, такий як IList<T>, ви більше загрожуєте порушити зміни коду.

Цитуючи джерело ,

ICollection, ICollection<T>: Ви хочете змінити колекцію або дбаєте про її розмір. IList, IList<T>: Ви хочете змінити колекцію, і ви дбаєте про впорядкування та / або розміщення елементів колекції.


5

Повернення типу інтерфейсу є більш загальним, тому (не маючи додаткової інформації щодо конкретного випадку використання) я б схилявся до цього. Якщо ви хочете виставити підтримку індексації, виберіть IList<T>, інакше ICollection<T>буде достатньо. Нарешті, якщо ви хочете вказати, що повернені типи лише для читання, виберіть IEnumerable<T>.

І якщо ви цього раніше не читали, Бред Абрамс та Кшиштоф Чваліна написали чудову книгу під назвою "Рамкові рекомендації щодо дизайну: конвенції, ідіоми та шаблони для багаторазових бібліотек .NET" (ви можете завантажити дайджест тут ).


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

-3

З цього питання випливають деякі теми:

  • інтерфейси та класи
  • який клас, з декількох однакових класів, колекція, список, масив?
  • Загальні класи по колекціях субітемів ("generics")

Ви можете підкреслити, що це об’єктно-орієнтований API

інтерфейси та класи

Якщо ви не маєте великого досвіду роботи з інтерфейсами, рекомендую дотримуватися класів. Я бачу багато разів, коли розробники стрибають на інтерфейси, навіть якщо це не потрібно.

І, нарешті, зробити поганий дизайн інтерфейсу замість хорошого дизайну класу, який, до речі, з часом може бути перенесений на хороший дизайн інтерфейсу ...

Ви побачите безліч інтерфейсів в API, але не поспішайте з ним, якщо він вам не потрібен.

Ви з часом навчитеся застосовувати інтерфейси до свого коду.

який клас, з декількох однакових класів, колекція, список, масив?

У c # (dotnet) є кілька класів, які можна змінювати. Як уже зазначалося, якщо вам потрібно щось із більш конкретного класу, наприклад, "CanBeSortedClass", то зробіть це явним у вашому API.

Чи дійсно вашому користувачеві API потрібно знати, що ваш клас можна сортувати або застосувати до елементів певний формат? Потім використовуйте "CanBeSortedClass" або "ElementsCanBePaintedClass", інакше використовуйте "GenericBrandClass".

В іншому випадку використовуйте більш загальний клас.

Загальні класи колекцій проти колекцій підтеми ("generics")

Ви виявите, що є класи, які містять інші елементи, і ви можете вказати, що всі елементи повинні бути певного типу.

Загальні колекції - це ті класи, які ви можете використовувати одну і ту ж колекцію для декількох програм коду, не створюючи нової колекції, для кожного нового типу підпункту, наприклад: Збірник .

Чи потрібен вашому користувачеві API дуже специфічний тип, однаковий для всіх елементів?

Використовуйте щось подібне List<WashingtonApple>.

Чи буде потрібно вашому користувачеві API кілька пов’язаних типів?

Expose List<Fruit>для API, і використовувати List<Orange> List<Banana>, List<Strawberry>всередині, де Orange, Bananaі Strawberryє нащадками Fruit.

Чи потребує ваш користувач API колекцію загального типу?

Використовуйте List, де всі предмети object.

Ура.


7
"Якщо ви не маєте великого досвіду роботи з інтерфейсами, рекомендую дотримуватися класів" Це не дуже корисна порада. Це як сказати, якщо є щось, чого ви не знаєте, просто тримайтеся подалі від цього. Інтерфейси надзвичайно потужні і підтримують концепцію "Програма на абстракції проти конкрементів". Вони також досить потужні для тестування одиниць через можливість обміну типами через макети. Існує маса інших вагомих причин використовувати інтерфейси поза цими коментарями, тому будь-ласка, будь ласка, вивчіть їх.
atconway

@atconway Я регулярно використовую інтерфейси, але, як і багато понять, це потужний інструмент, його можна легко перемішати. І іноді розробнику це може не знадобитися. Моя пропозиція не була "ніколи не інтерфейси", моя пропозиція була "в цьому випадку ви можете пропустити інтерфейси. Дізнайтеся про це, і ви можете отримати найкраще з них пізніше". Ура.
umlcat

1
Я рекомендую завжди програмувати на інтерфейс. Це допомагає тримати код вільно поєднаним.
mac10688

@ mac10688 Моя попередня відповідь (atconway) також стосується вашого коментаря.
umlcat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.