Если я напишу что-то вроде этого:
var things = mythings
.Where(x => x.IsSomeValue)
.Where(y => y.IsSomeOtherValue)
Это так же, как:
var results1 = new List<Thing>();
foreach(var t in mythings)
if(t.IsSomeValue)
results1.Add(t);
var results2 = new List<Thing>();
foreach(var t in results1)
if(t.IsSomeOtherValue)
results2.Add(t);
Или под покровами есть какая-то магия, которая работает примерно так:
var results = new List<Thing>();
foreach(var t in mythings)
if(t.IsSomeValue && t.IsSomeOtherValue)
results.Add(t);
Или это что-то совершенно другое?
Ответы:
LINQ-запросы ленивы . Это означает, что код:
очень мало Исходный enumerable (
mythings
) перечисляется только тогда, когда полученный enumerable (things
) используется, например,foreach
циклом.ToList()
или.ToArray()
.Если вы звоните
things.ToList()
, это примерно эквивалентно вашему последнему коду, возможно, с некоторыми (обычно незначительными) издержками от перечислителей.Аналогично, если вы используете цикл foreach:
По производительности он похож на:
Некоторые из преимуществ производительности при использовании метода лени для перечислимых данных (в отличие от вычисления всех результатов и сохранения их в списке) заключаются в том, что он использует очень мало памяти (поскольку за один раз сохраняется только один результат) и что значительного стоимость.
Если перечислимое перечислено только частично, это особенно важно. Рассмотрим этот код:
Способ реализации LINQ
mythings
будет перечисляться только до первого элемента, который соответствует вашим условиям. Если этот элемент находится в начале списка, это может значительно повысить производительность (например, O (1) вместо O (n)).источник
foreach
заключается в том, что LINQ использует вызовы делегатов, которые имеют некоторые накладные расходы. Это может быть важно, когда условия выполняются очень быстро (что они часто делают).ToList
илиToArray
. Если бы такая вещь была должным образом встроенаIEnumerable
, можно было бы попросить список, чтобы «сделать снимок» любых аспектов, которые могут измениться в будущем, без необходимости создавать все.Следующий код:
Ничто не эквивалентно, из-за ленивой оценки ничего не произойдет.
Отличается, потому что оценка будет запущена.
Каждый предмет
mythings
будет отдан первомуWhere
. Если оно пройдет, оно будет передано второмуWhere
. Если он пройдет, он будет частью вывода.Так что это выглядит примерно так:
источник
Отложенное выполнение в стороне (что уже объясняют другие ответы, я просто укажу на другую деталь), это больше похоже на ваш второй пример.
Давайте просто представьте , что вы звоните
ToList
наthings
.Реализация
Enumerable.Where
возвращает аEnumerable.WhereListIterator
. Когда вы вызываетеWhere
этоWhereListIterator
(также называемое цепочкойWhere
-calls), вы больше не звонитеEnumerable.Where
, ноEnumerable.WhereListIterator.Where
, который фактически объединяет предикаты (используяEnumerable.CombinePredicates
).Так что это больше похоже
if(t.IsSomeValue && t.IsSomeOtherValue)
.источник
Нет, это не то же самое. В вашем примере
things
- это объектIEnumerable
, который на данный момент является всего лишь итератором, а не фактическим массивом или списком. Более того, посколькуthings
он не используется, цикл даже не оценивается. ТипIEnumerable
позволяет перебирать элементыyield
-ed с помощью инструкций Linq и обрабатывать их далее с помощью большего количества инструкций, что означает, что в итоге у вас действительно будет только один цикл.Но как только вы добавляете инструкцию типа
.ToArray()
или.ToList()
, вы заказываете создание фактической структуры данных, тем самым ограничивая вашу цепочку.См. Этот связанный вопрос SO: /programming/2789389/how-do-i-implement-ienumerable
источник