В чем разница между IEnumerable и Array, IList и List?


92

В чем разница между IEnumerableи Array?

В чем разница между IListи List?

Похоже, они выполняют ту же функцию.

Ответы:


101

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

Array - это коллекция фиксированного размера с произвольным доступом (т.е. вы можете индексировать ее).

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

IList - это интерфейс, который абстрагирует функции списка (подсчет, добавление, удаление, доступ к индексатору) от различных конкретных классов, таких как List, BindingList, ObservableCollection и т. Д.


2
Согласно принятому ответу здесь stackoverflow.com/questions/1826658/… . Подсчет IEnumerable почти такой же, потому что он пытается вызвать Count на ICollection
— Карстен

15
Да и нет. Метод расширения Count () проверяет, является ли IEnumerable также ICollection, и вызывает его свойство Count, если это так. Если IEnumerable не является ICollection, тогда Count () возвращается к повторению последовательности. Возможно, мне следовало сказать, что «очень неэффективно подсчитывать элементы, используя только IEnumerable», потому что, конечно, наличие IEnumerable не мешает вам использовать более эффективный метод, если он есть у объекта, а методы расширения LINQ to Objects делают именно это. что.
— itowlson 07

17

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

Массив - это внутренняя часть .NET. Он содержит предметы одного типа, но фиксированного размера. После создания массива с x элементами он не может увеличиваться или уменьшаться.

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

List реализует интерфейс IList; это конкретный тип списка.

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

И IList, и массивы реализуют IEnumerable. Вот как работают интерфейсы - классы реализуют контракт и ведут себя аналогичным образом, и в результате с ними можно обращаться аналогичным образом (вы знаете, что класс реализует IEnumerable, вам не нужно знать, как или почему). Я предлагаю вам почитать об интерфейсах и так далее.


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

9

IEnumerable и IList - это интерфейсы . Массив и список - это классы. Массив реализует IEnumerable. List реализует IList, который расширяет IEnumerable.

Изменить: как Итоулсон упомянул в комментарии, Array также реализует IList.


Я тоже! Но что произойдет, если вы начнете вызывать IList.RemoveAt для массива?
— Марк Симпсон

Выдает исключение NotSupportedException. (Обратите внимание, что IList.RemoveAt и аналогичные методы IList явно реализованы в массиве, поэтому они не отображаются в обычных ссылках на массив!)
— itowlson

1
@itowlson Я хотел бы указать, что только System.Array (класс как таковой) реализует IList, но T [] не реализует IList <T>. T [] вместо этого реализует IEnumerable <T>. Я думаю, что OP не обязательно запрашивал System.Array при запросе «Array» ИМХО
— usr-local-ΕΨΗΕΛΩΝ 08

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()вызывается с элементом « A », проходящим сначала по конвейеру, а затем с элементом « B », следовательно, на выходе отображается « AABB », а во втором примере .Where()выполняется каждый раз , если мы называем .ToList()сразу же после этого, следовательно , видя « CD » , а затем « CD » снова, выход в два раза. Следовательно, каждый раз, когда Enumerable преобразуется в список или массив, будет стоить одной итерации O (n) по всем элементам в коллекции, что будет влиять на производительность, когда коллекция большая.

Хотя мы не склонны писать код таким образом, вызывая .ToList()между вызовами LINQ, это произойдет чаще, когда мы разложим код на повторно используемые методы, которые возвращают, List/Arrayа не IEnumerable.

Однако это не означает, что мы всегда должны действовать IEnumerable. Как упоминал Towlson , такие операции, как .Count()вызовут итерацию по коллекции, в то время как List или Array будут предварительно рассчитывать эту информацию, и поэтому преобразование в List/Arrayбудет более эффективным, если вы планируете вызывать .Count()несколько раз. Вот почему .Countв списке есть свойство, а не .Count()метод его подсчета.


0

Это старый пост, но все же думал об ответе. IEnumerable - это поведение, а массив - это структура данных (непрерывная коллекция элементов фиксированного размера, облегчающая доступ к элементам по индексам). Когда массив реализует IEnumerable, предполагается, что он также отображает внутреннее свойство IEnumerable (для облегчения итерации по коллекции).

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.