¿Cuáles son los beneficios de una ejecución diferida en LINQ?

Resuelto user702769 asked hace 13 años • 3 respuestas

LINQ utiliza un modelo de ejecución diferida, lo que significa que la secuencia resultante no se devuelve en el momento en que se llaman los operadores de Linq, sino que estos operadores devuelven un objeto que luego genera elementos de una secuencia solo cuando enumeramos este objeto.

Si bien entiendo cómo funcionan las consultas diferidas, tengo problemas para comprender los beneficios de la ejecución diferida:

1) He leído que la consulta diferida que se ejecuta solo cuando realmente necesita los resultados puede ser de gran beneficio. Entonces, ¿cuál es este beneficio?

2) Otra ventaja de las consultas diferidas es que si define una consulta una vez, cada vez que enumere los resultados, obtendrá resultados diferentes si los datos cambian.

a) Pero como se ve en el código siguiente, podemos lograr el mismo efecto (por lo tanto, cada vez que enumeramos el recurso, obtenemos un resultado diferente si los datos cambian) incluso sin usar consultas diferidas:

List<string> sList = new List<string>( new[]{ "A","B" });

foreach (string item in sList)
    Console.WriteLine(item); // Q1 outputs AB

sList.Add("C");

foreach (string item in sList)
    Console.WriteLine(item); // Q2 outputs ABC

3) ¿Existen otros beneficios de la ejecución diferida?

user702769 avatar Sep 07 '11 00:09 user702769
Aceptado

El principal beneficio es que esto permite que las operaciones de filtrado, el núcleo de LINQ, sean mucho más eficientes. (Este es efectivamente su elemento número 1).

Por ejemplo, tome una consulta LINQ como esta:

 var results = collection.Select(item => item.Foo).Where(foo => foo < 3).ToList();

Con la ejecución diferida, lo anterior itera su colección una vez y cada vez que se solicita un elemento durante la iteración, realiza la operación del mapa, filtra y luego usa los resultados para construir la lista.

Si hiciera que LINQ se ejecutara completamente cada vez, cada operación ( Select/ Where) tendría que recorrer toda la secuencia. Esto haría que las operaciones encadenadas fueran muy ineficientes.

Personalmente, diría que el punto número 2 anterior es más un efecto secundario que un beneficio; si bien a veces es beneficioso, a veces también causa cierta confusión, por lo que simplemente consideraría esto como "algo que hay que entender" y No lo promocione como un beneficio de LINQ.


En respuesta a su edición:

En su ejemplo particular, en ambos casos, Select iteraría la colección y devolvería un IEnumerable I1 de tipo item.Foo. Where() luego enumeraría I1 y devolvería IEnumerable<> I2 de tipo item.Foo. Luego, I2 se convertiría en Lista.

Esto no es cierto: la ejecución diferida evita que esto ocurra.

En mi ejemplo, el tipo de retorno es IEnumerable<T>, lo que significa que es una colección que se puede enumerar , pero, debido a la ejecución diferida, en realidad no se enumera.

Cuando llamas ToList(), se enumera toda la colección. El resultado termina pareciéndose conceptualmente a algo más parecido (aunque, por supuesto, diferente):

List<Foo> results = new List<Foo>();
foreach(var item in collection)
{
    // "Select" does a mapping
    var foo = item.Foo; 

    // "Where" filters
    if (!(foo < 3))
         continue;

    // "ToList" builds results
    results.Add(foo);
}

La ejecución diferida hace que la secuencia en sí solo se enumere (foreach) una vez , cuando se usa (por ToList()). Sin ejecución diferida, se parecería más (conceptualmente):

// Select
List<Foo> foos = new List<Foo>();
foreach(var item in collection)
{
    foos.Add(item.Foo);
}

// Where
List<Foo> foosFiltered = new List<Foo>();
foreach(var foo in foos)
{
    if (foo < 3)
        foosFiltered.Add(foo);
}    

List<Foo> results = new List<Foo>();
foreach(var item in foosFiltered)
{
    results.Add(item);
}
Reed Copsey avatar Sep 06 '2011 17:09 Reed Copsey

Otro beneficio de la ejecución diferida es que permite trabajar con series infinitas. Por ejemplo:

public static IEnumerable<ulong> FibonacciNumbers()
{
    yield return 0;
    yield return 1;

    ulong previous = 0, current = 1;
    while (true)
    {
        ulong next = checked(previous + current);
        yield return next;
        previous = current;
        current = next;

    }
}

(Fuente: http://chrisfulstow.com/fibonacci-numbers-iterator-with-csharp-yield-statements/ )

Luego puedes hacer lo siguiente:

var firstTenOddFibNumbers = FibonacciNumbers().Where(n=>n%2 == 1).Take(10);
foreach (var num in firstTenOddFibNumbers)
{
    Console.WriteLine(num);
}

Huellas dactilares:

1
1
3
5
13
21
55
89
233
377

Sin ejecución diferida, obtendría una OverflowExceptiono, si la operación no lo fuera, checkedse ejecutaría infinitamente porque se repite (y si la invocara, eventualmente ToListcausaría una )OutOfMemoryException

Davy8 avatar Sep 06 '2011 18:09 Davy8

Un beneficio importante de la ejecución diferida es que recibe datos actualizados. Esto puede afectar el rendimiento (especialmente si se trata de conjuntos de datos absurdamente grandes), pero igualmente los datos pueden haber cambiado cuando la consulta original arroja un resultado. La ejecución diferida garantiza que obtendrá la información más reciente de la base de datos en escenarios en los que la base de datos se actualiza rápidamente.

Nick Patsaris avatar Apr 14 '2013 15:04 Nick Patsaris