Чи слід завжди повертати IEnumerable <T> замість IList <T>?


97

Коли я пишу свій DAL або інший код, який повертає набір елементів, чи завжди повинен робити заяву про повернення:

public IEnumerable<FooBar> GetRecentItems()

або

public IList<FooBar> GetRecentItems()

На даний момент у своєму коді я намагаюся якомога більше використовувати IEnumerable, але я не впевнений, чи це найкраща практика? Це здавалося правильним, тому що я повертав найзагальніший тип даних, але все ще описував те, що він робить, але, можливо, це неправильно робити.


1
Це Список <T> або IList <T>, про який Ви запитуєте? Заголовок та запитання говорять по-різному ...
Фредрік Мерк, 02

Коли це можливо, користувальницький інтерфейс IEnumerable або Ilist instaead типу concerete.
Усман Масуд, 02

2
Я повертаю колекції як List <T>. Я не бачу необхідності повертати IEnumberable <T>, оскільки ви можете витягнути це зі Списку <T>
Чак Конвей


можливий дублікат ienumerablet-as-return-type
nawfal

Відповіді:


44

Це справді залежить від того, чому ви використовуєте саме цей інтерфейс.

Наприклад, IList<T>має кілька методів, яких немає в IEnumerable<T>:

  • IndexOf(T item)
  • Insert(int index, T item)
  • RemoveAt(int index)

та властивості:

  • T this[int index] { get; set; }

Якщо вам потрібні ці методи яким-небудь чином, то неодмінно поверніть IList<T>.

Крім того, якщо метод, який споживає ваш IEnumerable<T>результат, очікує значення IList<T>, це позбавить CLR від розгляду будь-яких необхідних перетворень, тим самим оптимізуючи скомпільований код.


2
@Jon FDG рекомендую використовувати Collection <T> або ReadOnlyCollection <T> як повернене значення для типів колекцій, дивіться мою відповідь.
Sam Saffron

Ви не зрозуміли, що ви маєте на увазі у своєму останньому реченні, коли ви говорите "це врятує CLR". Що врятує його, використовуючи IEnumerable проти IList? Чи можете ви це зрозуміти?
PositiveGuy

1
@CoffeeAddict Через три роки після цієї відповіді, я думаю, ви маєте рацію - ця остання частина невизначена. Якщо метод, який очікує IList <T> як параметр, отримує IEnumerable <T>, IEnumerable повинен бути обернутий вручну в новий List <T> або інший реалізатор IList <T>, і ця робота не буде виконана CLR для вас. Навпаки - метод, який очікує, що IEnumerable <T> отримає IList <T>, можливо, доведеться виконати деяку розпаковування, але, заднім числом, може не знадобитися, оскільки IList <T> реалізує IEnumerable <T>.
Jon Limjap

68

Вказівки щодо дизайну фреймворку рекомендують використовувати клас Collection, коли потрібно повернути колекцію, яка може бути змінена абонентом або ReadOnlyCollection для колекцій лише для читання.

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

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

Майте на увазі, що LINQ містить кілька хитрощів і буде оптимізувати певні дзвінки на основі типу, за яким вони виконуються. Отже, наприклад, якщо ви виконуєте a, Countа основна колекція - це Список, вона НЕ пройде всі елементи.

Особисто для ORM я б, напевно, дотримувався Collection<T>як своє повернене значення.


12
В Рекомендації по колекції містить більш детальний список децентралізованих і Donts.
Chaquotay

26

Загалом, вам слід вимагати найзагальніше і повертати найбільш конкретну річ, яку ви можете. Отже, якщо у вас є метод, який приймає параметр, і вам дійсно потрібне лише те, що доступне в IEnumerable, тоді це повинен бути ваш тип параметра. Якщо ваш метод міг повернути IList або IEnumerable, віддайте перевагу поверненню IList. Це гарантує, що він може бути використаний найширшим колом споживачів.

Будьте вільними в тому, що вам потрібно, і чітко висловлюйтесь у тому, що ви надаєте.


1
Я дійшов протилежного висновку: слід приймати конкретні типи, а повертати загальні типи. Однак ви можете мати рацію, що широко застосовувані методи кращі, ніж більш обмежені методи. Мені доведеться думати про це більше.
Dave Cousineau

3
Обґрунтуванням прийняття загальних типів як вхідних даних є те, що воно дозволяє працювати з якомога ширшим діапазоном введення для того, щоб отримати якомога більше повторного використання компонента. З іншого боку, оскільки ви вже точно знаєте, який предмет у вас є під рукою, маскувати його немає великого сенсу.
Мел

1
Думаю, що я погоджуюсь із наявністю більш загальних параметрів, але якими є ваші міркування щодо повернення чогось менш загального?
Dave Cousineau

6
Гаразд, дозвольте мені спробувати це по-іншому. Чому б ви викидати інформацію? Якщо ви дбаєте лише про те, щоб результат був IEnumerable <T>, чи боляче вам боляче знати, що це IList <T>? Ні, це не так. У деяких випадках це може бути зайвою інформацією, але це не завдасть вам шкоди. Тепер на користь. Якщо ви повернете List або IList, я можу негайно сказати, що колекція вже отримана, чогось я не можу знати за допомогою IEnumerable. Це може бути чи не корисна інформація, але ще раз, чому б ви викидали інформацію? Якщо ви знаєте додаткову інформацію про щось, передайте її.
Мел,

Ретроспективно, я здивований, що мене ніхто ніколи не дзвонив. IEnumerable WOULD вже отримано. Однак IQueryable можна повернути в незліченному стані. Тож, можливо, кращим прикладом було б повернення IQueryable проти IEnumerable. Повернення більш конкретного IQueryable дозволило б робити подальший склад, тоді як повернення IEnumerable змусило би негайно перерахувати і зменшило складність методу.
Мел

23

Це залежить...

Повернення найменш похідного типу ( IEnumerable) дасть вам найбільший простір для зміни базової реалізації за маршрутом.

Повернення більш похідного типу ( IList) надає користувачам вашого API більше операцій з результатом.

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


приємна загальна відповідь, оскільки це стосується і інших методів.
user420667

1
Я знаю, що ця відповідь дуже стара ... але, схоже, суперечить документації: "Якщо ні інтерфейс IDictionary <TKey, TValue>, ні інтерфейс IList <T> не відповідають вимогам необхідної колекції, виведіть новий клас колекції з натомість інтерфейс ICollection <T> для більшої гнучкості "Це, мабуть, означає, що слід віддавати перевагу більш похідному типу. ( msdn.microsoft.com/en-us/library/92t2ye13(v=vs.110).aspx ) \
DeborahK

11

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


@ Джоел Мюллер, я, як правило, телефоную до них ToList (). Я взагалі не люблю піддавати IQueryable решті своїх проектів.
KingNestor 02

4
Я більше мав на увазі LINQ-to-Objects, де IQueryable зазвичай не входить у зображення. Коли задіяна база даних, ToList () стає більш необхідним, оскільки в іншому випадку ви ризикуєте закрити з'єднання перед ітерацією, що працює не дуже добре. Однак, коли це не проблема, досить легко виставити IQueryable як IEnumerable, не вимагаючи додаткової ітерації, коли ви хочете приховати IQueryable.
Джоел Мюллер 02

9

List<T>пропонує телефонний код набагато більше можливостей, таких як модифікація поверненого об'єкта та доступ за допомогою індексу. Тож питання зводиться до: у конкретному випадку використання вашої програми, ХОЧИТЕ ЛИ підтримувати такі використання (імовірно, повертаючи щойно створену колекцію!), Для зручності абонента - або ви хочете швидкість для простого випадку, коли всі абоненту потрібно прокрутити колекцію, і ви можете безпечно повернути посилання на справжню базову колекцію, не боячись, що це призведе до помилкової зміни тощо?

Тільки ви можете відповісти на це запитання, і лише добре зрозумівши, що ваші абоненти хочуть зробити із поверненим значенням, і наскільки тут важлива продуктивність (наскільки великі колекції ви б копіювали, наскільки це може бути вузьким місцем, тощо).


"безпечно повернути посилання на справжню базову колекцію, не побоюючись, що це призведе до помилкової зміни" - Навіть якщо ви повернете IEnumerable <T>, чи не могли вони просто повернути його до списку <T> і змінити?
Кобі 02

Не кожен IEnumarable <T> також є списком <T>. Якщо повернений об'єкт не має типу, який успадковується від List <T> або реалізує IList <T>, це призведе до InvalidCastException.
lowglider 02

2
List <T> має проблему , що вона блокує вас в конкретну реалізацію Collection <T> або ReadOnlyCollection <T> Кращі
Sam Saffron

4

Я думаю, ви можете використовувати будь-яку, але кожна має свою користь. В основному Listце так, IEnumerableале у вас є функція підрахунку, додавання елемента, видалення елемента

IEnumerable не є ефективним для підрахунку елементів

Якщо колекція призначена лише для читання, або модифікація колекції контролюється Parentтоді поверненням IListпросто для Countне є хорошою ідеєю.

У Linq існує Count()метод розширення, для IEnumerable<T>якого всередині CLR буде ярлик, .Countякщо базовий тип має IList, тож різниця в продуктивності незначна.

Як правило, я вважаю (думка), що кращою практикою є повернення IEnumerable, де це можливо, якщо вам потрібно зробити доповнення, то додайте ці методи до батьківського класу, інакше споживач керує колекцією в рамках Моделі, що порушує принципи, наприклад, manufacturer.Models.Add(model)порушує закон деметер. Звичайно, це лише рекомендації, а не жорсткі правила, але поки ви не зрозумієте застосовності, сліпо слідувати краще, ніж взагалі не дотримуватися.

public interface IManufacturer 
{
     IEnumerable<Model> Models {get;}
     void AddModel(Model model);
}

(Примітка: Якщо ви використовуєте nNHibernate, можливо, вам доведеться зіставити приватний IList, використовуючи різні засоби доступу.)


2

Це не так просто, коли ви говорите про повернені значення замість вхідних параметрів. Коли це вхідний параметр, ви точно знаєте, що вам потрібно зробити. Отже, якщо вам потрібно мати можливість переглядати колекцію, ви берете IEnumberable, тоді як якщо вам потрібно додати чи видалити, ви берете IList.

У випадку поверненого значення це жорсткіше. Що очікує ваш абонент? Якщо ви повернете IEnumerable, він апріорі не буде знати, що він може зробити з нього ІЛІСТ. Але, якщо ви повернете ІЛіста, він буде знати, що він може переглядати його. Отже, ви повинні врахувати, що ваш абонент збирається робити з даними. Функціональність, яка потрібна / очікується вашим абонентом, - це те, що має керуватися при прийнятті рішення про те, що повернути.


0

як усі вже говорили, це залежить, якщо ви не хочете додавати / видаляти функціональність на викликовому шарі, тоді я проголосую за IEnumerable, оскільки він забезпечує лише ітерацію та базові функціональні можливості, які в перспективі дизайну мені подобаються. Повернувшись IList, мої голоси завжди повторюються, але це головним чином те, що вам подобається, а що ні. з точки зору продуктивності я думаю, що вони більше однакові.


0

Якщо ви не враховуєте у своєму зовнішньому коді, завжди краще повернути IEnumerable, тому що пізніше ви можете змінити свою реалізацію (без впливу зовнішнього коду), наприклад, для логіки ітератора врожаю та збереження ресурсів пам'яті (до речі дуже гарна функція мови ).

Однак якщо вам потрібна кількість елементів, не забувайте, що між IEnumerable та IList є ще один шар - ICollection .


0

Я, можливо, трохи переживаю тут, бачачи, що досі ніхто цього не пропонував, але чому б вам не повернути (I)Collection<T> ?

Наскільки я пам’ятаю, Collection<T>був кращим типом повернення, List<T>оскільки він абстрагує реалізацію. Всі вони реалізують IEnumerable, але це звучить для мене занадто низьким рівнем для роботи.


0

Я думаю, ви можете використовувати будь-яку, але кожна має свою користь. В основному Listє, IEnumerableале у вас є функція підрахунку, Додати елемент, Вилучити елемент

IEnumerable неефективний для підрахунку елементів або отримання певного елемента в колекції.

List це колекція, яка ідеально підходить для пошуку конкретних елементів, легко додавання елементів або їх видалення.

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

Використовуйте, List<FooBar> getRecentItems() а не IList<FooBar> GetRecentItems()


0

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

Тим не менш, я думаю, що важливіше врахувати код перед собою, який ви пишете, ніж код, який напише наступний хлопець (в межах розумної причини.) Це тому, що ви можете робити припущення щодо коду, який вже існує.

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

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


0

TL; ЛІКАР; - резюме

  • Якщо ви розробляєте власне програмне забезпечення, використовуйте певний тип (Like List) для повернутих значень і найбільш загальний тип для вхідних параметрів, навіть у випадку колекцій.
  • Якщо метод є частиною загальнодоступного API бібліотеки, що розповсюджується, використовуйте інтерфейси замість конкретних типів колекції, щоб ввести як повернені значення, так і вхідні параметри.
  • Якщо метод повертає колекцію лише для читання, покажіть це, використовуючи IReadOnlyListабо IReadOnlyCollectionяк тип поверненого значення.

Більше

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.