LINQ и IEnumerable<T>
является тянуть на основе. Это означает, что предикаты и действия, которые являются частью оператора LINQ вообще, не выполняются до тех пор, пока значения не будут вытащены. Кроме того, предикаты и действия будут выполняться каждый раз, когда значения вытягиваются (например, не происходит тайного кэширования).
вытягивать из IEnumerable<T>
делаются на foreach
заявлении, которое на самом деле является синтаксическим сахаром для получения перечислителя по телефону IEnumerable<T>.GetEnumerator()
и неоднократно вызывая IEnumerator<T>.MoveNext()
тянуть значение.
LINQ операторы, такие как ToList()
, ToArray()
, ToDictionary()
и ToLookup()
обертывания foreach
заявления, чтобы эти методы будут делать тянуть. То же самое можно сказать и о таких операторах, как Aggregate()
, Count()
и First()
. Эти методы имеют общий характер, что они создают один результат, который должен быть создан путем выполнения инструкции foreach
.
Многие операторы LINQ производят новую последовательность IEnumerable<T>
. Когда элемент вытягивается из результирующей последовательности, оператор вытягивает один или несколько элементов из исходной последовательности. Оператор Select()
является наиболее очевидным примером, но другие примеры: SelectMany()
, Where()
, Concat()
, Union()
, Distinct()
, Skip()
и Take()
. Эти операторы не выполняют кэширование. Когда затем N-й элемент вытягивается из Select()
, он вытягивает N-й элемент из исходной последовательности, применяет проекцию с помощью прилагаемого действия и возвращает ее. Ничего секретного здесь не происходит.
Другие операторы LINQ также производят новые последовательности IEnumerable<T>
, но они реализованы путем фактического вытягивания всей последовательности источников, выполнения их работы и последующего создания новой последовательности. Эти методы включают Reverse()
, OrderBy()
и GroupBy()
. Однако притяжение оператора выполняется только тогда, когда сам оператор вытаскивается, что означает, что вам все еще нужен цикл foreach
«в конце» оператора LINQ, прежде чем что-либо будет выполнено. Вы можете утверждать, что эти операторы используют кеш, потому что они сразу же извлекают всю исходную последовательность. Тем не менее, этот кеш создается каждый раз, когда оператор повторяется, так что это действительно деталь реализации, а не то, что волшебным образом обнаружит, что вы применяете одну и ту же операцию OrderBy()
несколько раз к той же последовательности.
В вашем примере ToList()
будет тянуть. Действие во внешнем Select
будет выполняться 100 раз. Каждый раз, когда это действие выполняется, Aggregate()
выполнит еще одно нажатие, которое будет анализировать атрибуты XML. В общей сложности ваш код будет звонить Int32.Parse()
200 раз.
Вы можете улучшить это, потянув атрибуты один раз вместо того, чтобы на каждой итерации:
var X = XElement.Parse (@"
<ROOT>
<MUL v='2' />
<MUL v='3' />
</ROOT>
")
.Elements()
.Select (t => Int32.Parse (t.Attribute ("v").Value))
.ToList();
Enumerable.Range (1, 100)
.Select (s => x.Aggregate (s, (t, u) => t * u))
.ToList()
.ForEach (s => Console.WriteLine (s));
Теперь Int32.Parse()
вызывалась только в 2 раза. Однако стоимость заключается в том, что список значений атрибутов должен быть выделен, сохранен и в конечном итоге собран из мусора. (Не очень важно, когда список содержит два элемента.)
Обратите внимание, что если вы забудете первый ToList()
, который вытаскивает атрибуты, код все равно будет работать, но с теми же характеристиками, что и исходный код. Для хранения атрибутов не используется пробел, но они обрабатываются на каждой итерации.
«Как бы я хотел разобраться в чем-то подобном себе» - лучший снимок - это изучение ИЛ, созданного из этого кода. – Andrey
Вы можете установить точку останова отладчика в методе Parse() и посмотреть, как часто она попадает. –