Como funciona a seguinte instrução LINQ?

160

Como funciona a seguinte instrução LINQ ?

Aqui está o meu código:

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

Resultado: 2, 4, 6, 8

Por que não 2, 4, 6?

Atish Dipongkor - MVP
fonte
102
O resultado de uma expressão de consulta é uma consulta, não a execução da consulta.
21813 Eric Lippert
6
Para menos informações, consulte a resposta aceita para esta pergunta .
Daniel
9
Certamente você pode pensar em um título que realmente resume a questão.
Matt Bola
2
Meu palpite sobre os votos negativos (6 até agora, não meus) é que eles consideram o título da pergunta genérico demais para ser uma boa pergunta. Mas, vendo o número de votos positivos e se tornando a principal questão da semana no boletim, não acho que você precise se preocupar muito com isso.
Abel

Respostas:

235

A saída é 2,4,6,8devido à execução adiada .

A consulta é realmente executada quando a variável de consulta é repetida, não quando a variável de consulta é criada. Isso é chamado de execução adiada.

- Suprotim Agarwal, "Execução de consulta adiada versus imediata no LINQ"

Há outra execução chamada Immediate Query Execution , que é útil para armazenar em cache os resultados da consulta. De Suprotim Agarwal novamente:

Para forçar a execução imediata de uma consulta que não produz um valor singleton, você pode chamar o método ToList(), ToDictionary(), ToArray(), Count(), Average()ou Max()em uma variável de consulta ou consulta. Eles são chamados de operadores de conversão que permitem que você faça uma cópia / captura instantânea do resultado e o acesso seja quantas vezes você desejar, sem a necessidade de executar novamente a consulta.

Se você deseja que a saída seja 2,4,6, use .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);
 }
Atish Dipongkor - MVP
fonte
8
Count (), Max (), Avg (), Sum () e provavelmente outros métodos que precisam levar a lista inteira em consideração, também causam avaliação da consulta.
Kenned
1
Eu sempre pensei em ter, digamos, 'filterList' como uma variável, em vez de 'filterList ()' como um método - a ideia é que você itere cada vez que deseja filtrar a lista, em vez de chamar um método. Pode ser um método interessante, embora incomum e talvez imperfeito em termos de desempenho.
Katana314
4
@Sebastian - Para além @ o comentário de Kenned, .First(), .FirstOrDefault(), .Single()e .SingleOrDefault()também desencadear a avaliação da consulta.
21913 Scotty.NET
4
surpreendente como você obteve a resposta em menos de 30s: D
MC
2
@ MC Não sei por que você está fazendo essa pergunta. A resposta completa não foi dada de cada vez. Foi editado várias vezes.
Atish Dipongkor - MVP
11

Isso aconteceu devido à execução adiada, o que significa que o cálculo da expressão não é executado até que seja necessário em algum lugar. Isso melhora o desempenho se os dados forem muito grandes.

Sandeep Chauhan
fonte
3
Você pode descobrir isso, pois também pode significar que sua enumeração cara está sendo executada várias vezes. Nesse caso, você pode até sofrer perda de desempenho.
Careta do Desespero
0

A razão para isso é a execução adiada da sua expressão lambda. A consulta é executada quando você inicia a iteração no loop foreach.

Prateek Dhuper
fonte
11
Tecnicamente, é a execução adiada do iterador , não a lambda .
D # Stanley
0

Quando você usa um IEnumerable <> obtido do LINQ, apenas é criada uma classe Enumerator e a iteração é iniciada apenas quando você a usa em alguma caminhada.

Miguel
fonte
-1

Você está obtendo esse resultado devido à execução adiada, o que significa que o resultado não é avaliado até o primeiro acesso.

Para tornar mais claro, adicione 10 à lista no final do seu snipet e, em seguida, imprima novamente, você não obterá 10 na saída

     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);
    }
sandeep
fonte
Você realmente tentou isso? Eu entro 10na saída.
precisa
good catch @MarkHurd yes não adicionou .ToList (). editou o post agora deve dar a saída esperada. Minha expectativa era expressão é avaliada somente quando você usar o var para o primeiro tempo, mas parece que ele está sendo avaliada toda vez
sandeep
Agora não conterá 8em nenhuma saída.
27613 Mark Hurd