¿Cómo funciona la siguiente instrucción LINQ?

160

¿Cómo funciona la siguiente instrucción LINQ ?

Aquí está mi 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);
}

Salida: 2, 4, 6, 8

¿Por qué no 2, 4, 6?

Atish Dipongkor - MVP
fuente
102
El resultado de una expresión de consulta es una consulta, no la ejecución de la consulta.
Eric Lippert
66
Para obtener menos información, consulte la respuesta aceptada a esta pregunta .
Daniel
9
Seguramente puedes pensar en un título que realmente resuma la pregunta.
Matt Ball
2
Mi suposición acerca de los votos negativos (6 por ahora, no los míos) es que consideran que el título de la pregunta es demasiado genérico para ser una buena pregunta. Pero, viendo el número de votos a favor y convirtiéndose en la pregunta principal de la semana en el boletín, no creo que deba preocuparse demasiado.
Abel

Respuestas:

235

La salida es 2,4,6,8a causa de la ejecución diferida .

La consulta se ejecuta realmente cuando la variable de consulta se repite, no cuando se crea la variable de consulta. Esto se llama ejecución diferida.

- Suprotim Agarwal, "Ejecución de consulta diferida vs inmediata en LINQ"

Hay otra ejecución llamada Ejecución de consulta inmediata , que es útil para almacenar en caché los resultados de la consulta. De Suprotim Agarwal nuevamente:

Para forzar la ejecución inmediata de una consulta que no produce un valor singleton, puede llamar al método ToList(), ToDictionary(), ToArray(), Count(), Average()o Max()en una consulta o variable de consulta. Estos se denominan operadores de conversión que le permiten hacer una copia / instantánea del resultado y el acceso es tantas veces como lo desee, sin la necesidad de volver a ejecutar la consulta.

Si desea que la salida sea 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
fuente
8
Count (), Max (), Avg (), Sum () y probablemente otros métodos que deben tener en cuenta toda la lista, también causan la evaluación de la consulta.
Kenned
1
A menudo he pensado en tener, por ejemplo, 'filterList' como una variable, en lugar de 'filterList ()' como método; la idea es que iteras sobre ella cada vez que deseas filtrar la lista, en lugar de llamar a un método. Podría ser un método interesante, aunque inusual y quizás imperfecto en cuanto al rendimiento, para hacer las cosas.
Katana314
44
@Sebastian - En relación con el comentario de @ Kenned, .First(), .FirstOrDefault(), .Single()y .SingleOrDefault()también desencadenar la evaluación de la consulta.
Scotty.NET
44
asombroso cómo obtuviste la respuesta en menos de 30 segundos: D
MC
2
@MC No sé por qué haces esta pregunta. No se dio una respuesta completa a la vez. Fue editado varias veces.
Atish Dipongkor - MVP
11

Esto ha sucedido debido a la ejecución diferida, lo que significa que el cálculo de la expresión no se ejecuta hasta que se necesita en algún lugar. Esto mejora el rendimiento si los datos son demasiado grandes.

Sandeep Chauhan
fuente
3
Puede matizar eso, ya que también puede significar que su costosa enumeración se ejecuta varias veces. En tal caso, incluso podría sufrir una pérdida de rendimiento.
Mueca de desesperación
0

La razón de esto es la ejecución diferida de su expresión lambda. La consulta se ejecuta cuando comienza a iterar en el bucle foreach.

Prateek Dhuper
fuente
11
Técnicamente es la ejecución diferida del iterador , no la lambda .
D Stanley
0

Cuando usa un IEnumerable <> obtenido de LINQ, solo se crea una clase Enumerator y la iteración solo comienza cuando la usa en alguna caminata.

Miguel
fuente
-1

Obtendrá este resultado debido a la ejecución diferida, lo que significa que el resultado no se evalúa hasta su primer acceso.

Para que quede más claro, solo agregue 10 a la lista al final de su snipet y luego imprima nuevamente, no obtendrá 10 en la salida

     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
fuente
¿Realmente intentaste eso? Me pongo 10en la salida.
Mark Hurd
buena captura @MarkHurd sí no agregó .ToList (). editó la publicación ahora, debería dar el resultado esperado. Mi expectativa era expresión se evalúa sólo cuando se utiliza el var por primera vez, pero parece como si cada vez que se está evaluado
Sandeep
Ahora no contendrá 8en ninguna salida.
Mark Hurd