Коли використовувати IList та коли використовувати List


180

Я знаю, що IList - це інтерфейс, а Список - конкретний тип, але я досі не знаю, коли використовувати їх. Я зараз роблю, якщо мені не потрібні методи Сортування або Знайти всі, я використовую інтерфейс. Маю рацію? Чи є кращий спосіб вирішити, коли використовувати інтерфейс або конкретний тип?


1
Якщо хто - то до сих пір цікаво, я знаходжу кращі відповіді тут: stackoverflow.com/questions/400135/listt-or-ilistt
Crismogram

Відповіді:


175

Я дотримуюся двох правил:

  • Прийміть найосновніший тип, який буде працювати
  • Поверніть найбагатший тип, який вам знадобиться користувачеві

Отже, коли ви пишете функцію чи метод, який бере колекцію, запишіть її не на Список, а на IList <T>, ICollection <T> або IEnumerable <T>. Загальні інтерфейси все ще працюватимуть навіть для гетерогенних списків, оскільки System.Object може бути і T. Це допоможе вам врятувати головний біль, якщо ви вирішите використовувати стек або іншу структуру даних далі вниз. Якщо все, що вам потрібно зробити у функції, - це передбачити її, IEnumerable <T> - це дійсно все, про що ви повинні просити.

З іншого боку, повертаючи об'єкт із функції, ви хочете надати користувачеві найбагатший можливий набір операцій, без того, щоб їх потрібно було виконувати. Тож у такому випадку, якщо це Список <T> внутрішньо, поверніть копію як Список <T>.


43
Не слід по-різному ставитися до типів вводу / виводу. Обидва типи введення та виведення повинні бути самим основним типом (бажано інтерфейсом), який підтримуватиме потреби клієнтів. Інкапсуляція покладається на те, щоб якомога менше розповідати клієнтам про реалізацію вашого класу. Якщо ви повернете конкретний список, ви не зможете перейти на інший кращий тип, не змушуючи всіх своїх клієнтів повторно складати / оновлювати.
Еш

11
Я не погоджуюся з двома правилами ... Я б використовував найпримітивніший тип та спеціальність при поверненні в цьому випадку IList (краще IEnumarable), і вам слід працювати зі списком у вашій функції всередині. Потім, коли вам потрібно "додати" або "сортувати", тоді використовуйте колекцію, якщо потрібно більше, тоді використовуйте "Список". Тож моїм твердим правилом було б: ПОЧАЙТЕ завжди з НЕЧІЛЬКОГО, і якщо вам потрібно більше, тоді продовжте ...
ethem

2
Для вашої зручності «два правила» мають назву: принцип стійкості (він же закон Постеля) .
easoncxz

Незалежно від того, в якій стороні дискусії хтось веде питання про те, повертати найосновніший чи найбагатший тип, слід врахувати, що, повертаючи дуже спрощений інтерфейс, споживчий код може часто - хоча і не завжди - використовувати if...elseланцюжок із isключовим словом, щоб визначити набагато багатший тип для нього, і в кінцевому підсумку киньте його і все одно скориставшись цим. Тож вам не обов’язково гарантувати що-небудь приховувати за допомогою базового інтерфейсу, а не просто затемнювати це. Однак ускладнення може також змусити письменника споживчого коду подумати над тим, як вони його використовують.
Panzercrisis

6
Я дуже не погоджуюся з пунктом №2, особливо якщо це на кордоні послуги / api. Повернення модифікованих колекцій може створити враження, що колекції є "живими" та методами виклику, як, наприклад, Add()і Remove()можуть мати наслідки, крім простої колекції. Повернення інтерфейсу лише для читання, такого як IEnumerableчасто це спосіб пошуку методів пошуку даних. Ваш споживач може спроектувати його у більш багатий тип за потребою.
STW

56

Вказівки Майкрософт, як це перевірено FxCop, перешкоджають використанню List <T> у публічних API - віддайте перевагу IList <T>.

Між іншим, я зараз майже завжди оголошую одновимірні масиви як IList <T>, що означає, що я можу послідовно використовувати властивість IList <T> .Count, а не Array.Length. Наприклад:

public interface IMyApi
{
    IList<int> GetReadOnlyValues();
}

public class MyApiImplementation : IMyApi
{
    public IList<int> GetReadOnlyValues()
    {
        List<int> myList = new List<int>();
        ... populate list
        return myList.AsReadOnly();
    }
}
public class MyMockApiImplementationForUnitTests : IMyApi
{
    public IList<int> GetReadOnlyValues()
    {
        IList<int> testValues = new int[] { 1, 2, 3 };
        return testValues;
    }
}

3
Мені найбільше подобається це пояснення / приклад!
JonH

28

Є важлива річ, яку люди, здається, завжди не помічають:

Ви можете передати звичайний масив до чогось, що приймає IList<T>параметр, а потім ви можете зателефонувати IList.Add()і отримаєте виняток з виконання:

Unhandled Exception: System.NotSupportedException: Collection was of a fixed size.

Наприклад, врахуйте наступний код:

private void test(IList<int> list)
{
    list.Add(1);
}

Якщо ви зателефонуєте так, ви отримаєте виняток із виконання:

int[] array = new int[0];
test(array);

Це відбувається тому, що використання простих масивів із IList<T>порушенням принципу заміни Ліскова.

З цієї причини, якщо ви телефонуєте, IList<T>.Add()ви можете розглянути питання про необхідність отриманняList<T> а IList<T>.


Це тривіально вірно для кожного інтерфейсу. Якщо ви хочете дотримуватися свого аргументу, тоді ви можете стверджувати, що взагалі ніколи не використовувати жоден інтерфейс, оскільки деяка його реалізація може кинути. Якщо ви, з іншого боку, вважаєте, що пропозиція ОП надає перевагу List<T>над IList<T>, ви також повинні бути в курсі причин, чому IList<T>рекомендується. (Наприклад blogs.msdn.microsoft.com/kcwalina/2005/09/26/… )
Micha Wiedenmann

3
@MichaWiedenmann Моя відповідь тут конкретна, коли ви телефонуєте IList<T>.Add(). Я не кажу, що не слід використовувати IList<T>- я лише вказую на можливий підводний камінь. (Я вважаю за краще використовувати IEnumerable<T>або IReadOnlyList<T>чи IReadOnlyCollection<T>в перевазі , IList<T>якщо я можу.)
Метью Уотсон

24

Я погодився б із порадою Лі щодо прийняття параметрів, але не повернення.

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

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


22

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

IList
IList інвентар IEnumerable.
Ви повинні використовувати, IListколи вам потрібен доступ до колекції за індексом, додавання та видалення елементів тощо ...

Список
List реалізацій IList.


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

9

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

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


1
Завжди краще прийняти найменший можливий базовий тип. Повернення - це інша історія. Виберіть, які варіанти можуть бути корисними. Отже, ви думаєте, що ваш клієнт може захотіти використовувати індексований доступ? Захистіть їх від того, як ToList()ви повернулися, IEnumerable<T>що вже був у списку, і поверніть IList<T>натомість Тепер клієнти можуть отримати вигоду від того, що ви можете забезпечити без зусиль.
Тимо

5

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


4

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

Наприклад, скажімо, у вас є Personклас та Groupклас. GroupПримірник має багато людей, так що список тут матиме сенс. Коли я декларую об'єкт списку, Groupя буду використовувати IList<Person>та інстанціювати його як List.

public class Group {
  private IList<Person> people;

  public Group() {
    this.people = new List<Person>();
  }
}

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


3
чому б не зробити його в першу чергу просто Списком? Я досі не розумію, чому бонус ви отримуєте за те, щоб зробити його IList, тоді в конструкторі ви перетворюєте його на Список <>
chobo2

Я згоден, якщо ви явно створюєте об'єкт List <T>, то ви втрачаєте перевагу інтерфейсу?
The_Butcher

4

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

Однак у .NET 2.0 є прикро - у IList немає методу Sort () . Ви можете використовувати адаптер, що постачається в комплекті:

ArrayList.Adapter(list).Sort()

2

Ви повинні використовувати інтерфейс лише в тому випадку, якщо він вам потрібен, наприклад, якщо ваш список передається для іншої реалізації IList, крім List. Це вірно, коли, наприклад, ви використовуєте NHibernate, який кидає ILists в об'єкт сумки NHibernate під час отримання даних.

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


1

У ситуаціях, які зазвичай трапляються, я рідко використовую IList безпосередньо.

Зазвичай я просто використовую це як аргумент методу

void ProcessArrayData(IList almostAnyTypeOfArray)
{
    // Do some stuff with the IList array
}

Це дозволить мені робити загальну обробку майже на будь-якому масиві .NET Framework, якщо він не використовує IEnumerable, а не IList, що буває іноді.

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


1

Об'єкт AList дозволяє створювати список, додавати до нього речі, видаляти, оновлювати, індексувати його і т. Д. Список використовується завжди, коли ви просто хочете загальний список, де ви вказуєте його тип об'єкта, і це все.

IList з іншого боку - це інтерфейс. В основному, якщо ви хочете створити свій власний тип списку, скажімо клас списку під назвою BookList, тоді ви можете використовувати Інтерфейс, щоб дати вам основні методи та структуру для нового класу. IList призначений для створення власного спеціального підкласу, який реалізує List.

Ще одна відмінність полягає в тому, що IList - це інтерфейс, і його неможливо створити. Список є класом і може бути екземпляром. Це означає:

IList<string> MyList = new IList<string>();

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