Яка різниця між IEnumerable та Array, IList та List?


91

Яка різниця між IEnumerableта Array?

Яка різниця між IListта List?

Здається, вони мають однакову функцію.

Відповіді:


101

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

Масив - це колекція фіксованого розміру з довільним доступом (тобто ви можете індексувати її).

Список - це колекція змінного розміру (тобто ви можете додавати та видаляти елементи) із довільним доступом.

IList - це інтерфейс, який абстрагує функціональність списку (підрахувати, додати, видалити, отримати доступ до індексатора) за межами різних конкретних класів, таких як List, BindingList, ObservableCollection тощо.


2
Відповідно до прийнятої відповіді тут stackoverflow.com/questions/1826658/… . Розраховувати на IEnumerable - це майже те саме, що він намагається назвати Count on ICollection
Карстен

15
Так і ні. Метод розширення Count () перевіряє, чи IEnumerable також є ICollection, і викликає його властивість Count, якщо він є. Якщо IEnumerable не є ICollection, то Count () повертається до ітерації послідовності. Можливо, я повинен був сказати "дуже неефективно рахувати елементи, використовуючи лише IEnumerable", оскільки, звичайно, наявність IEnumerable не заважає використовувати більш ефективний метод, якщо об'єкт його має, а методи розширення LINQ to Objects роблять саме що.
itowlson

17

IEnumerable - це інтерфейс, який дозволяє ітерацію через колекцію елементів (наприклад, за допомогою ключового слова foreach).

Масив - це власне .NET. У ньому зберігаються предмети одного типу, але він має фіксований розмір. Після створення масиву з елементами x він не може зростати або стискатися.

IList визначає інтерфейс для списку, а також реалізує IEnumerable.

Список реалізує інтерфейс IList; це конкретний тип списку.

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

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


1
Додаючи цей коментар для читачів, які не читають усіх відповідей: масиви та List <> обидва реалізують IList <> (масиви реалізують більшу частину цього явно, мабуть, тому, що багато членів створюють NotSupportedException). Також правильніше говорити, що IList успадковує від IEnumerable, ніж говорити, що він його реалізує.
phoog

9

IEnumerable та IList - це інтерфейси . Масив і Список - це класи. Масив реалізує IEnumerable. Список реалізує IList, який розширює IEnumerable.

Змінити: як згадував itowlson у коментарі, Array також реалізує IList.


Як і я! Що станеться, якщо ви почнете викликати IList.RemoveAt на масиві?
Марк Сімпсон

Він видає NotSupportedException. (Примітка. IList.RemoveAt та подібні методи IList явно реалізовані в Array, щоб вони не відображалися на звичайному посиланні на Array!)
itowlson,

1
@itowlson Я б зазначив, що лише System.Array (клас як такий) реалізує IList, але T [] не реалізує IList <T>. Натомість T [] реалізує IEnumerable <T>. Я думаю, що OP не обов’язково просив System.Array, коли запитував "Array" IMHO
usr-local-ΕΨΗΕΛΩΝ

6

Покоління колекції IEnumerable є лінивим. Приклад:

public IEnumerable<int> GetTwoInts()
{
  yield return 1;
  yield return 2;
}
public void Something()
{
  var twoInts = GetTwoInts();
}

У методі Something виклик GetTwoInts () насправді не призведе до виконання методу GetTwoInts, оскільки перерахування ніколи не повторюється.


Не могли б ви трохи його докласти? Я не впевнений, чи правильно я це зрозумів. Чи можете ви показати рядок коду пізніше, що фактично призведе до виконання цього методу? Дякую!
paaone

@paaone помістити twoIntsв foreachабо виклику twoInts.ToList().
row1

2

IEnumerable- це загальний інтерфейс, який використовується багатьма класами, наприклад Array, Listі Stringдля того, щоб дозволити комусь переглядати колекцію. В основному, це те, що рухає foreachтвердження.

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


2

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

Це тому, що ітератор, що повертається, List<T>.GetEnumeratorє типом значення, тоді як той, що повертається, IList<T>.GetEnumeratorє посилальним типом, і, отже, вимагає виділення пам'яті (див. Перерахувач типу значення списку в c # ).

На мій погляд, IList<T>це не дуже хороший інтерфейс. Наприклад, виклик Addможе кидати (див. Чому масив реалізує IList? ). Якщо вам потрібна інкапсуляція, вам краще скористатися IEnumerable<T>або IReadOnlyList<T>.


1

На додаток до інших відповідей, розуміння різниці між Enumerable та List / Array при використанні LINQ може мати величезний вплив на продуктивність. Коротше кажучи, Enumerable може сприйматись як конструктор запитів, тоді як List / Array є результатом запиту.

У контексті LINQ to SQL за допомогою EntityFramework, перший просто створює SQL-запит, не виконуючи його відносно бази даних і не завантажуючи будь-які дані в пам'ять, тоді як другий - навпаки. Ось чому ми будемо відкладати виклик .ToList()доти, поки він нам не знадобиться в пам'яті для виконання бізнес-логіки.

В іншому контексті вираз LINQ, що повертає IEnumerable, буде відкладати виконання до .ToList()виклику. Розглянемо приклад нижче:

void Main()
{
    var w1 = "AB".AsEnumerable();
    Console.WriteLine($"W1: 1");
    w1 = w1.Where(W1);
    Console.WriteLine($"W1: 2");
    w1 = w1.Where(W2);
    Console.WriteLine($"W1: 3");
    w1.ToList();    
    Console.WriteLine($"----------");

    var w2 = "CD".AsEnumerable();
    Console.WriteLine($"W2: 1");
    w2 = w2.Where(W1);
    Console.WriteLine($"W2: 2");
    w2 = w2.ToList();
    Console.WriteLine($"W2: 3");
    w2 = w2.Where(W2);
    Console.WriteLine($"W2: 4");
    w2.ToList();    
    Console.WriteLine($"----------");
}

bool W1(char arg)
{
    Console.WriteLine($"W1:{arg}");
    return true;
}

bool W2(char arg)
{
    Console.WriteLine($"W2:{arg}");
    return true;
}

OUTPUT:
W1: 1
W1: 2
W1: 3
W1:A
W2:A
W1:B
W2:B
----------
W2: 1
W2: 2
W1:C
W1:D
W2: 3
W2: 4
W2:C
W2:D
----------

У першому прикладі два .Where()"додаються" і виконуються разом наприкінці, коли .ToList()виклик з елементом " А " проходить по трубі першим, а потім елементом " В ", отже, на виході бачиться " AABB ", тоді як у другому прикладі, .Where()виконується кожен раз , якщо ми називаємо .ToList()відразу ж після цього, отже , бачачи « CD » , а потім « CD » знову, вихід в два рази. Отже, кожен раз, коли Enumerable перетворюється на Список або Масив, буде коштувати одну O (n) ітерацію над усіма елементами колекції, що матиме вплив на продуктивність, коли колекція велика.

Хоча ми не маємо тенденції писати код таким чином, щоб дзвонити .ToList()між викликами LINQ, але це траплялося б частіше, коли ми враховуємо код для методів багаторазового використання, які повертаються, List/Arrayа не IEnumerable.

Однак це не означає, що нам слід завжди оперувати IEnumerable. Як згадував towlson , такі операції, .Count()які спричинять ітерацію над колекцією, тоді як у Списку чи Масиві ця інформація буде попередньо розрахована, і тому перетворення на List/Arrayнеї буде більш ефективним, якщо ви плануєте дзвонити .Count()кілька разів. Ось чому .Countв списку є властивість, а не .Count()спосіб його підрахунку.


0

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

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