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


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



foreach (Animal animal in sel) { /*do stuff*/ }



  1. я заметил, что если я использую IEnumerable, когда я отлаживаю и проверяю "sel", который в этом случае является IEnumerable, у него есть некоторые интересные участники: "внутреннее", "наружное", "innerKeySelector" и "outerKeySelector", эти последние 2 представляется делегатов. "Внутренний" член имеет в себе не" животные "экземпляры, а скорее" видовые " экземпляры, что было очень странно для меня. "Внешний" член содержит" животные " экземпляры. Я полагаю, что два делегата определяют, что входит и что выходит из него?


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


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



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



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



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

1006   9  

9 ответов:

IEnumerable описывает поведение, в то время как список является реализацией этого поведения. Когда вы используете 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());

так это быстрее использовать список над 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-нет.

посмотреть практическая разница между 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() );

играть вокруг

https://repl.it/E8Ki/0

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

Comments

    Ничего не найдено.