Как работает следующий оператор LINQ?

160

Как работает следующий оператор LINQ ?

Вот мой код:

var list = new List<int>{1,2,4,5,6};
var even = list.Where(m => m%2 == 0);
list.Add(8);
foreach (var i in even)
{
    Console.WriteLine(i);
}

Вывод: 2, 4, 6, 8

Почему нет 2, 4, 6?

Атиш Дипонгкор - MVP
источник
102
Результатом выражения запроса является запрос, а не его выполнение.
Эрик Липперт
6
Для получения дополнительной информации см. Принятый ответ на этот вопрос .
Даниэль
9
Конечно, вы можете придумать заголовок, который фактически суммирует вопрос.
Мэтт Болл
2
Мое предположение о отрицательных голосах (6 к настоящему времени, не мое) состоит в том, что они считают название вопроса слишком общим, чтобы быть хорошим вопросом. Но, учитывая количество голосов и становясь главным вопросом недели в новостной рассылке, я не думаю, что вам нужно слишком беспокоиться об этом.
Авель

Ответы:

235

Выход 2,4,6,8из-за отложенного выполнения .

Запрос фактически выполняется, когда переменная запроса перебирается, а не когда переменная запроса создается. Это называется отложенным исполнением.

- Супротим Агарвал, «Отложенное и немедленное выполнение запросов в LINQ»

Существует еще одно выполнение, называемое Immediate Query Execution , которое полезно для кэширования результатов запросов. Из Супротима снова Агарвал:

Чтобы принудительно выполнить запрос, который не выдает одноэлементное значение, можно вызвать метод ToList(), ToDictionary(), ToArray(), Count(), Average()или Max()для запроса или переменной запроса. Это так называемые операторы преобразования, которые позволяют вам сделать копию / снимок результата и получить доступ столько раз, сколько вам нужно, без необходимости повторного выполнения запроса.

Если вы хотите, чтобы вывод был 2,4,6, используйте .ToList():

var list = new List<int>{1,2,4,5,6};
var even = list.Where(m => m%2 == 0).ToList();
list.Add(8);
foreach (var i in even)
 {
    Console.WriteLine(i);
 }
Атиш Дипонгкор - MVP
источник
8
Count (), Max (), Avg (), Sum () и, возможно, другие методы, которые должны учитывать весь список, также вызывают оценку запроса.
Кенн
1
Я часто думал о том, чтобы, скажем, «FilterList» в качестве переменной, а не «FilterList ()» в качестве метода - идея заключается в том, что вы выполняете итерацию по нему каждый раз, когда хотите отфильтровать список, а не вызываете метод. Это может быть интересный, хотя и необычный и, возможно, несовершенный способ выполнения работы.
Katana314
4
@Sebastian - В дополнение к @ комментарий Kenned, в .First(), .FirstOrDefault(), .Single()а .SingleOrDefault()также вызвать вычисления запроса.
Scotty.NET
4
Удивительно, как вы получили ответ менее чем за 30 секунд: D
MC
2
@ MC Я не знаю, почему ты задаешь этот вопрос. Весь ответ не был дан за один раз. Он был отредактирован несколько раз.
Атиш Дипонгкор - MVP
11

Это произошло из-за отложенного выполнения, что означает, что вычисление выражения не выполняется, пока оно не понадобится где-то. Это повышает производительность, если данные слишком велики.

Сандип Чаухан
источник
3
Вы можете нюансировать это, поскольку это также может означать, что ваше дорогостоящее перечисление выполняется несколько раз. В таком случае вы можете даже потерять производительность.
Гримаса Отчаяния
0

Причиной этого является отложенное выполнение вашего лямбда-выражения. Запрос выполняется, когда вы начинаете итерацию в цикле foreach.

Пратик Дхупер
источник
11
Технически это отложенное выполнение итератора , а не лямбда .
D Стэнли
0

Когда вы используете IEnumerable <>, полученный из LINQ, создается только класс Enumerator, и итерация начинается только тогда, когда вы используете его в некоторой прогулке.

Miguel
источник
-1

Вы получаете этот результат из-за отложенного выполнения, что означает, что результат фактически не оценивается до его первого доступа.

Чтобы сделать это более понятным, просто добавьте 10 к списку в конце вашего фрагмента и затем напечатайте снова, вы не получите 10 в выводе

     var list = new List<int>{1,2,4,5,6};
    var even = list.Where(m => m%2 == 0).Tolist();
    list.Add(8);
    foreach (var i in even)
    {
        Console.WriteLine(i);
    }
//new*
    list.Add(10);
    foreach (var i in even)
    {
        Console.WriteLine(i);
    }
Сандип
источник
Вы действительно пробовали это? Я получаю 10в выходной.
Марк Херд
хороший улов @MarkHurd да не добавлено .ToList (). отредактировал пост, теперь он должен дать ожидаемый результат. Я ожидал, что выражение вычисляется только при первом использовании var, но похоже, что оно вычисляется каждый раз
sandeep
Теперь он не будет содержаться 8ни в одном выводе.
Марк Херд