2012-11-06 7 views
20

Ho un elenco di elementi e una query LINQ su di essi. Ora, con l'esecuzione differita di LINQ, un ciclo foreach successivo eseguirà la query solo una volta o per ogni giro del ciclo?Foreach esegue la query solo una volta?

Dato questo esempio (Tratto da Introduction to LINQ Queries (C#), on MSDN)

// The Three Parts of a LINQ Query: 
    // 1. Data source. 
    int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 }; 

    // 2. Query creation. 
    // numQuery is an IEnumerable<int> 
    var numQuery = 
     from num in numbers 
     where (num % 2) == 0 
     select num; 

    // 3. Query execution. 
    foreach (int num in numQuery) 
    { 
     Console.Write("{0,1} ", num); 
    } 

O, in altre parole, ci sarebbe alcuna differenza se avessi:

foreach (int num in numQuery.ToList()) 

E, sarebbe la materia, se i dati sottostanti non sono in un array, ma in un Database?

risposta

15

Ora, con l'esecuzione posticipata di LINQ, un ciclo foreach successivo eseguirà la query solo una volta o per ogni giro del ciclo?

Sì, una volta per il ciclo. In realtà, è possibile eseguire la query meno di una volta: è possibile interrompere il ciclo parziale e il test (num % 2) == 0 non verrà eseguito su nessun elemento rimanente.

O, in altre parole, ci sarebbe alcuna differenza se avessi:

foreach (int num in numQuery.ToList()) 

due differenze:

  1. Nel caso di cui sopra, ToList() sprechi di tempo e di memoria , perché per prima cosa fa la stessa cosa del foreach iniziale, crea un elenco da esso, e quindi l'elenco foreach s. Le differenze saranno da qualche parte tra banali e impedendo al codice di funzionare sempre, a seconda delle dimensioni dei risultati.

  2. Tuttavia, nel caso in cui si sta andando a ripetutamente fare foreach sugli stessi risultati, o altrimenti utilizzare ripetutamente, l'allora mentre il foreach corre solo la query una volta, la prossima foreach corre di nuovo. Se la query è costosa, allora l'approccio ToList() (e la memorizzazione di tale elenco) può essere un enorme risparmio.

+0

Sì, una volta. Forse anche meno di un'intera esecuzione della query, come spiega la modifica sopra. –

+0

Grazie per avermi spiegato in dettaglio! – Marcel

8

No, non fa differenza. L'espressione in viene valutata una volta. In particolare, il costrutto foreach richiama il metodo GetEnumerator() sull'espressione in e chiama ripetutamente lo MoveNext() e accede alla proprietà Current per attraversare lo IEnumerable.

OTOH, chiamare ToList() è ridondante. Non dovresti preoccuparti di chiamarlo.

Se l'input è una banca dati, la situazione è leggermente diversa, dato che LINQ uscite IQueryable, ma sono abbastanza sicuro che tratta i foreach ancora come un IEnumerable (che eredita IQueryable).

+0

... ed è per questo motivo che viene lanciata un'eccezione nel tentativo di cambiare la collezione in cui '' foreach'-through. – Alex

+0

@Alex o rigorosamente, perché * può * essere lanciato. Le regole per 'foreach' non promettono che la modifica sarà consentita, ma le collezioni sono libere di permetterle e talvolta devono (quelle progettate per l'uso concorrente sarebbero bloccate se dovessero proibire le modifiche simultanee da altri thread). –

2

Come scritto, ogni iterazione del ciclo farebbe esattamente tutto il lavoro necessario per recuperare il risultato successivo. Quindi la risposta sarebbe tecnicamente "nessuna delle precedenti". La query verrebbe eseguita "in pezzi".

Se si utilizza ToList() o qualsiasi altro metodo materializzazione (ToArray() ecc), allora la query sarà valutata una volta sul posto e successive operazioni (come l'iterazione per i risultati) si limiterà a lavorare su una lista di "stupido".

Se numbers erano una IQueryable invece di un IEnumerable - come sarebbe probabilmente in uno scenario di base di dati - allora quanto sopra è ancora vicino alla verità, anche se non è una descrizione perfettamente accurata. In particolare, al primo tentativo di materializzare un risultato, il provider interrogabile dovrebbe parlare al database e produrre un set di risultati; quindi, le righe da questo set di risultati verrebbero estratte a ogni iterazione.

+0

Grazie per aver spiegato la parte del DB. – Marcel

1

La query LINQ sarà eseguito quando viene enumerato (sia come risultato di una chiamata .ToList() o fare un foreach sui risultati.

Se si sta enumerando i risultati della query LINQ due volte, entrambe le volte causerà l'interrogazione dell'origine dati (nell'esempio, l'enumerazione della raccolta) poiché restituisce solo uno IEnumerable. Tuttavia, a seconda della query di linq, potrebbe non sempre elencare l'intera raccolta (ad esempio, .Any() e .Single() fermarsi sul primo oggetto o sul primo oggetto corrispondente se c'è un .Where()).

I dettagli di implementazione di un provider LINQ possono differire in modo che il comportamento usuale quando l'origine dati è un database è chiamare .ToList() subito per cache i risultati della query & inoltre verificare che la query (nel caso di EF, L2S o NHibernate) è eseguito una volta lì e quindi anziché quando la raccolta viene enumerata in un momento successivo nel codice e per impedire che la query venga eseguita più volte se i risultati vengono enumerati più volte.