Quali sono i vantaggi di un’esecuzione differita in LINQ?

LINQ utilizza un modello di esecuzione differita che significa che la sequenza risultante non viene restituita al momento della chiamata degli operatori Linq, ma invece questi operatori restituiscono un object che quindi restituisce gli elementi di una sequenza solo quando enumeriamo questo object.

Mentre capisco come funzionano le query posticipate, sto avendo qualche problema a comprendere i vantaggi dell’esecuzione posticipata:

1) Ho letto che la query posticipata in esecuzione solo quando hai effettivamente bisogno dei risultati può essere di grande beneficio. Allora, qual è questo vantaggio?

2) Altro vantaggio delle query posticipate è che se si definisce una query una volta, ogni volta che si enumerano i risultati, si ottengono risultati diversi se i dati cambiano.

a) Tuttavia, come visto dal codice sottostante, siamo in grado di ottenere lo stesso effetto (quindi ogni volta che enumeriamo la risorsa, otteniamo risultati diversi se i dati vengono modificati) anche senza utilizzare le query posticipate:

List sList = new List( 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) Ci sono altri benefici dell’esecuzione differita?

Il vantaggio principale è che questo consente alle operazioni di filtro, il nucleo di LINQ, di essere molto più efficienti. (Questo è effettivamente il tuo articolo n. 1).

Ad esempio, prendi una query LINQ come questa:

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

Con l'esecuzione posticipata, la procedura sopra riportata itera la tua raccolta una volta e ogni volta che viene richiesta una voce durante l'iterazione, esegue l'operazione della mappa, filtra, quindi utilizza i risultati per creare l'elenco.

Se dovessi eseguire LINQ completamente ogni volta, ogni operazione ( Select / Where ) dovrebbe ripetere l'intera sequenza. Ciò renderebbe molto inefficienti le operazioni concatenate.

Personalmente, direi che il tuo articolo 2 qui sopra è più un effetto collaterale piuttosto che un beneficio - mentre a volte è utile, a volte causa anche qualche confusione, quindi considererei solo questo "qualcosa da capire" e non pubblicizzarlo come vantaggio di LINQ.


In risposta alla tua modifica:

Nel tuo particolare esempio, in entrambi i casi Select seleziona iterate collection e restituisce un IEnumerable I1 di tipo item.Foo. Dove () dovrebbe quindi enumerare I1 e restituire IEnumerable <> I2 di tipo item.Foo. I2 verrebbe quindi convertito in Elenco.

Questo non è vero - l'esecuzione posticipata impedisce che ciò si verifichi.

Nel mio esempio, il tipo restituito è IEnumerable , il che significa che si tratta di una raccolta che può essere enumerata , ma, a causa dell'esecuzione posticipata, in realtà non è enumerata.

Quando chiami ToList() , l'intera raccolta viene enumerata. Il risultato finisce per sembrare concettualmente qualcosa di più (anche se, ovviamente, diverso):

 List results = new List(); 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); } 

L'esecuzione posticipata fa sì che la sequenza stessa venga enumerata (foreach) una sola volta , quando viene utilizzata (da ToList() ). Senza un'esecuzione differita, sembrerebbe più (concettualmente):

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

Un altro vantaggio dell’esecuzione posticipata è che consente di lavorare con serie infinite. Per esempio:

 public static IEnumerable 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; } } 

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

È quindi ansible effettuare le seguenti operazioni:

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

stampe:

1
1
3
5
13
21
55
89
233
377

Senza un’esecuzione posticipata, si otterrebbe una OverflowException o se l’operazione non fosse checked sarebbe eseguita all’infinito perché si avvolge (e se hai chiamato ToList una OutOfMemoryException alla fine)

Un vantaggio importante dell’esecuzione posticipata è che si ricevono dati aggiornati. Questo può essere un successo sulle prestazioni (specialmente se si hanno a che fare con insiemi di dati assurdamente grandi) ma allo stesso modo i dati potrebbero essere cambiati nel momento in cui la query originale restituisce un risultato. L’esecuzione posticipata garantisce l’acquisizione delle informazioni più recenti dal database in scenari in cui il database viene aggiornato rapidamente.