IEnumerable vs List-что использовать? Как они работают?

у меня есть некоторые сомнения по поводу того, как работают Перечислители и 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();

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

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

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

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

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

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

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

9 ответов


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

всякий раз, когда я "складываю" выражения 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 over IEnumerable? Только если вы хотите предотвратить выполнение запроса более одного раза. Но лучше ли в целом? Ну в вышеприведенном, леопарды и гиены превращаются в одиночные запросы SQL каждый, и база данных возвращает только соответствующие строки. Но если бы мы вернули список из AllSpotted(), тогда он может работать медленнее, потому что база данных может возвращать гораздо больше данных, чем на самом деле необходимо, и мы тратим циклы на фильтрацию в клиенте.

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

List<Animals> Leopards = Feline(AllSpotted()).ToList();
List<Animals> Hyenas = Canine(AllSpotted()).ToList();

класс, реализующий 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 ) ...

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

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

enter image description hereenter image description here


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

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

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


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

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

посмотреть практическая разница между списком и IEnumerable


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

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

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


если все вы хотите сделать, это перечислить их, используйте IEnumerable.

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


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

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() );

играть arround

https://repl.it/E8Ki/0


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