IEnumerable vs List - что использовать?Как они работают? - PullRequest
603 голосов
/ 02 сентября 2010

У меня есть некоторые сомнения по поводу того, как работают счетчики и LINQ.Рассмотрим эти два простых выбора:

List<Animal> sel = (from animal in Animals 
                    join race in Species
                    on animal.SpeciesKey equals race.SpeciesKey
                    select animal).Distinct().ToList();

или

IEnumerable<Animal> sel = (from animal in Animals 
                           join race in Species
                           on animal.SpeciesKey equals race.SpeciesKey
                           select animal).Distinct();

Я изменил имена своих исходных объектов, чтобы это выглядело как более общий пример.Сам запрос не так важен.Я хочу спросить вот что:

foreach (Animal animal in sel) { /*do stuff*/ }
  1. Я заметил, что если я использую IEnumerable, когда я отлаживаю и проверяю "sel", который в этом случае является IEnumerable,у него есть несколько интересных членов: «inner», «external», «innerKeySelector» и «outerKeySelector», последние 2 выглядят как делегаты.«Внутренний» член не имеет экземпляров «Animal», а содержит «Species», что было очень странно для меня.«Внешний» член содержит экземпляры «Animal».Я предполагаю, что два делегата определяют, что входит и что выходит из этого?

  2. Я заметил, что если я использую «Distinct», «inner» содержит 6 пунктов (это невернокак только 2 являются отличительными), но «внешний» содержит правильные значения.Опять же, вероятно, делегированные методы определяют это, но это немного больше, чем я знаю об IEnumerable.

  3. Самое главное, какой из двух вариантов является лучшим с точки зрения производительности?

Преобразование Злого Списка через .ToList()?

Или, может быть, с помощью счетчика напрямую?

Если вы можете, пожалуйста, объясните немного или киньте несколько ссылоккоторые объясняют это использование IEnumerable.

Ответы [ 10 ]

671 голосов
/ 02 сентября 2010

IEnumerable описывает поведение, в то время как List является реализацией этого поведения.Когда вы используете IEnumerable, вы даете компилятору возможность отложить работу на потом, возможно, оптимизируя его.Если вы используете ToList (), вы заставляете компилятор сразу преобразовывать результаты.

Всякий раз, когда я "складываю" выражения LINQ, я использую IEnumerable, потому что, только указав поведение, я даю LINQ шансотложить оценку и, возможно, оптимизировать программу.Помните, как LINQ не генерирует SQL для запроса базы данных, пока вы не перечислите его?Учтите это:

public IEnumerable<Animals> AllSpotted()
{
    return from a in Zoo.Animals
           where a.coat.HasSpots == true
           select a;
}

public IEnumerable<Animals> Feline(IEnumerable<Animals> sample)
{
    return from a in sample
           where a.race.Family == "Felidae"
           select a;
}

public IEnumerable<Animals> Canine(IEnumerable<Animals> sample)
{
    return from a in sample
           where a.race.Family == "Canidae"
           select a;
}

Теперь у вас есть метод, который выбирает начальный образец («AllSpotted»), а также некоторые фильтры.Итак, теперь вы можете сделать это:

var Leopards = Feline(AllSpotted());
var Hyenas = Canine(AllSpotted());

Так быстрее ли использовать List над IEnumerable?Только если вы хотите предотвратить выполнение запроса более одного раза.Но лучше ли это в целом?Как уже было сказано выше, леопарды и гиены преобразуются в отдельные запросы SQL каждый , а база данных возвращает только соответствующие строки.Но если бы мы вернули List из AllSpotted(), то он может работать медленнее, потому что база данных может вернуть гораздо больше данных, чем фактически необходимо, и мы тратим циклы, выполняя фильтрацию в клиенте.

В программеможет быть лучше отложить преобразование вашего запроса в список до самого конца, поэтому, если я собираюсь перечислять леопардов и гиен несколько раз, я бы сделал это:

List<Animals> Leopards = Feline(AllSpotted()).ToList();
List<Animals> Hyenas = Canine(AllSpotted()).ToList();
134 голосов
/ 04 июня 2015

Вот очень хорошая статья, написанная: Технический блог Клаудио Бернаскони здесь: Когда использовать IEnumerable, ICollection, IList и List

Вот некоторые основные сведения о сценариях и функциях:

enter image description here enter image description here

123 голосов
/ 02 сентября 2010

Класс, реализующий IEnumerable, позволяет использовать синтаксис foreach.

По сути, у него есть метод для получения следующего элемента в коллекции. Ему не нужно, чтобы вся коллекция находилась в памяти, и он не знает, сколько в нем предметов, foreach просто продолжает получать следующий предмет, пока он не закончится.

Это может быть очень полезно в определенных обстоятельствах, например, в массивной таблице базы данных вы не хотите копировать всю вещь в память перед началом обработки строк.

Теперь List реализует IEnumerable, но представляет всю коллекцию в памяти. Если у вас есть IEnumerable и вы звоните .ToList(), вы создаете новый список с содержимым перечисления в памяти.

Ваше выражение linq возвращает перечисление, и по умолчанию выражение выполняется, когда вы выполняете итерацию, используя foreach. Оператор IEnumerable linq выполняется при выполнении итерации foreach, но вы можете заставить его выполнять итерацию раньше, используя .ToList().

Вот что я имею в виду:

var things = 
    from item in BigDatabaseCall()
    where ....
    select item;

// this will iterate through the entire linq statement:
int count = things.Count();

// this will stop after iterating the first one, but will execute the linq again
bool hasAnyRecs = things.Any();

// this will execute the linq statement *again*
foreach( var thing in things ) ...

// this will copy the results to a list in memory
var list = things.ToList()

// this won't iterate through again, the list knows how many items are in it
int count2 = list.Count();

// this won't execute the linq statement - we have it copied to the list
foreach( var thing in list ) ...
78 голосов
/ 09 декабря 2014

Никто не упомянул одно принципиальное различие, иронично ответил на вопрос, закрытый как дублированный из этого.

IEnumerable доступен только для чтения, а List - нет.

См. Практическая разница между List и IEnumerable

65 голосов
/ 02 сентября 2010

Самое важное, что нужно понять, это то, что, используя Linq, запрос не оценивается сразу.Он запускается только как часть итерации полученного IEnumerable<T> в foreach - это то, что делают все странные делегаты.

Итак, первый пример оценивает запрос немедленно, вызывая ToList ипомещение результатов запроса в список.
Во втором примере возвращается IEnumerable<T>, который содержит всю информацию, необходимую для последующего выполнения запроса.

С точки зрения производительности ответ - itзависит .Если вам нужно, чтобы результаты оценивались сразу (скажем, вы изменяете структуры, которые запрашиваете позже, или если вы не хотите, чтобы итерация по IEnumerable<T> занимала много времени), используйте список.Еще используйте IEnumerable<T>.По умолчанию следует использовать оценку по требованию во втором примере, поскольку он обычно использует меньше памяти, если только нет особой причины для сохранения результатов в списке.

37 голосов
/ 02 сентября 2010

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

Если вы вызовете ToList, запрос будет выполнен или «материализован», как я хотел бы сказать.

Есть плюсы и минусы для обоих. Если вы вызываете ToList, вы можете удалить некоторую загадку относительно того, когда запрос будет выполнен. Если вы придерживаетесь IEnumerable, вы получаете преимущество в том, что программа не выполняет никакой работы, пока она на самом деле не требуется.

16 голосов
/ 26 ноября 2016

Я поделюсь одной неверно использованной концепцией, с которой я столкнулся в один день:

var names = new List<string> {"mercedes", "mazda", "bmw", "fiat", "ferrari"};

var startingWith_M = names.Where(x => x.StartsWith("m"));

var startingWith_F = names.Where(x => x.StartsWith("f"));


// updating existing list
names[0] = "ford";

// Guess what should be printed before continuing
print( startingWith_M.ToList() );
print( startingWith_F.ToList() );

Ожидаемый результат

// I was expecting    
print( startingWith_M.ToList() ); // mercedes, mazda
print( startingWith_F.ToList() ); // fiat, ferrari

Фактический результат

// what printed actualy   
print( startingWith_M.ToList() ); // mazda
print( startingWith_F.ToList() ); // ford, fiat, ferrari

Объяснение

Как и в других ответах, оценка результата была отложена до вызова ToList или аналогичных методов вызова, например ToArray.

Так что в этом случае я могу переписать код следующим образом:

var names = new List<string> {"mercedes", "mazda", "bmw", "fiat", "ferrari"};

// updating existing list
names[0] = "ford";

// before calling ToList directly
var startingWith_M = names.Where(x => x.StartsWith("m"));

var startingWith_F = names.Where(x => x.StartsWith("f"));

print( startingWith_M.ToList() );
print( startingWith_F.ToList() );

Play Arround

https://repl.it/E8Ki/0

15 голосов
/ 02 сентября 2010

Если вам нужно только перечислить их, используйте IEnumerable.

Однако следует помнить, что изменение перечисляемой исходной коллекции является опасной операцией - в этом случае сначала вам потребуется ToList. Это создаст новый элемент списка для каждого элемента в памяти, перечисляя IEnumerable, и, следовательно, будет менее производительным, если вы будете перечислять только один раз - но безопаснее, а иногда и методы List удобны (например, в произвольном доступе). *

5 голосов
/ 16 июня 2017

В дополнение ко всем ответам, опубликованным выше, вот мои два цента.Есть много других типов, кроме List, которые реализуют IEnumerable, такие как ICollection, ArrayList и т. Д. Поэтому, если у нас есть IEnumerable в качестве параметра любого метода, мы можем передать функцию любого типа коллекции.Т.е. у нас может быть метод для работы с абстракцией, а не какая-то конкретная реализация.

1 голос
/ 11 марта 2019

Во многих случаях (например, бесконечный список или очень большой список) IEnumerable не может быть преобразован в список. Наиболее очевидными примерами являются все простые числа, все пользователи Facebook с их деталями или все элементы на eBay.

Разница в том, что объекты "List" хранятся "прямо здесь и сейчас", тогда как объекты "IEnumerable" работают "только по одному за раз". Поэтому, если я просматриваю все элементы на eBay, то по одному будет то, что может справиться даже маленький компьютер, но «.ToList ()» наверняка выгонит меня из памяти, независимо от того, насколько большим был мой компьютер. Ни один компьютер сам по себе не может содержать и обрабатывать такое огромное количество данных.

[Редактировать] - Само собой разумеется - это не "или то или это". часто имеет смысл использовать как список, так и IEnumerable в одном классе. Ни один компьютер в мире не может перечислить все простые числа, потому что по определению это потребует бесконечного количества памяти. Но вы можете легко подумать о class PrimeContainer, который содержит IEnumerable<long> primes, который по понятным причинам также содержит SortedList<long> _primes. все простые числа рассчитаны до сих пор. следующее простое число, которое будет проверено, будет выполнено только для существующих простых чисел (до квадратного корня). Таким образом, вы получаете оба - простые числа по одному (IEnumerable) и хороший список «простых чисел на данный момент», что является довольно хорошим приближением всего (бесконечного) списка.

...