Вот мой ответ, но также обратите внимание, что Джон Скит говорил об этом сегодня в своем блоге и о том, что он не совсем согласен с MSDN, означающим «Ленивый», поскольку MSDN не совсем ясно, что именно означает «ленивый».когда они используют его в Насколько вы ленивы? его пост делает для интересного прочтения.
Дополнительно Википедия предполагает, что для ленивых вычислений должны быть соблюдены три правила ив MSDN не учитывается третья точка, так как выражение будет оцениваться более одного раза, если GetEnumerator
вызывается снова (по спецификации Reset не реализован для объектов перечислителя, сгенерированных с использованием ключевого слова yield
, и большинство linq используют его в настоящее время)
С учетом функции
int Computation(int index)
Немедленное выполнение
IEnumerable<int> GetComputation(int maxIndex)
{
var result = new int[maxIndex];
for(int i = 0; i < maxIndex; i++)
{
result[i] = Computation(i);
}
return result;
}
- При вызове функции
Computation
выполняется maxIndex
раз GetEnumerator
возвращает новый экземпляр перечислителя, больше ничего не делая. - Каждый вызов
MoveNext
помещает значение, сохраненное в следующем массиве.ячейка в Current
члене IEnumerator
и это все.
Стоимость : большой аванс, маленький при перечислении (только копия)
Отложенное, но нетерпеливое выполнение
IEnumerable<int> GetComputation(int maxIndex)
{
var result = new int[maxIndex];
for(int i = 0; i < maxIndex; i++)
{
result[i] = Computation(i);
}
foreach(var value in result)
{
yield return value;
}
}
- Когда вызывается функция, создается экземпляр автоматически сгенерированного класса (в спецификации называемого "перечислимым объектом"), реализующего
IEnumerable
, и создается копия аргумента.(maxIndex
) хранится в нем. GetEnumerator
возвращает новый экземпляр перечислителя, ничего больше не делая. - Первый вызов
MoveNext
выполняет maxIndex, умноженный на метод вычисления,сохраните результат в массиве и Current
вернет первое значение. - Каждый последующий вызов
MoveNext
приведет к Current
значению, сохраненному в массиве.
Стоимость : ничего заранее, Большой, когда начинается перечисление, Маленький во время перечисления (только копия)
Отложенное и ленивое выполнение
IEnumerable<int> GetComputation(int maxIndex)
{
for(int i = 0; i < maxIndex; i++)
{
yield return Computation(i);
}
}
- КогдаФункция вызывается так же, как и случай с отложенным выполнением.
GetEnumerator
возвращает новый экземпляр перечислителя, больше ничего не делая. - Каждый вызов
MoveNext
выполняется один раз с кодом Computation
, помещает значение в Current
и немедленно разрешает вызывающей сторонедействовать на результат.
Большинство linq используют отложенное и ленивое выполнение, но некоторые функции не могут быть похожи на сортировку.
Стоимость : ничего предоплата,Умеренный во время перечисления (вычисления выполняются там)
Подводя итог
- Немедленно означает, что вычисление / выполнение выполняется в функции и завершается после ее возврата.(Полностью рвение оценка, как это делает большинство кодов C #)
- Отложено / Стремление означает, что большая часть работы будет выполнена в первый
MoveNext
или когда IEnumerator
экземпляр создан (для IEnumerable
это когда GetEnumerator
вызывается) - Отложено / Ленивый означает, что работа будет выполняться каждый раз, когда вызывается
MoveNext
, но ничегоbefore.
Parallel LINQ делает это немного по-другому, поскольку вычисление можно считать отложенным / ленивым с точки зрения вызывающей стороны, но внутренне вычисление некоторого числа элементовначать параллельно, как только начнется перечисление.В результате, если следующее значение уже есть, вы получите его немедленно, но в противном случае вам придется его ждать.