2014-08-29 19 views
6

Diciamo che abbiamo list.Where(p=>p.Number > n).Select(p=> p.Name).Where(n=> n.StartsWith(a)).ToList(); verrà eseguito un algoritmo di un passaggio o sarà 3 passaggi?LINQ tenta di risolvere le cose con un solo passaggio?

+0

Diciamo che abbiamo un esame di stato e agli studenti viene chiesto di risolvere un semplice compito algoritmico che può essere risolto tramite C# LINQ in una semplice query semplice. Tuttavia, se non riescono a provare nelle corse in una sola passata, saranno considerati una soluzione non ottimale e gli studenti avranno cattivi voti. – Rella

+0

Si noti che esiste una domanda simile a Linq2SQL: la query risultante verrà eseguita utilizzando una singola istruzione 'SELECT'? Per domande semplici come il tuo esempio, se possono essere convertite in una query SQL la risposta è sì. Ma alcuni [complessi] (http://stackoverflow.com/q/22816591/256431) [query] (http://stackoverflow.com/q/12264751/256431) non lo fanno, e non so se ogni garanzia è data. –

risposta

6

Esso creerà l'elenco in un passaggio, a causa del modo in cui LINQ invia i dati.

Per esempio, prendete questo:

var query = list.Where(p => p.Number > n); 

Che di per sé non guarda qualsiasi degli elementi della lista. Invece, ricorda la lista che stai guardando, e quando inizi a scorrere su query, ogni volta che chiedi il prossimo elemento, controllerà gli elementi della lista a turno fino a quando non trova una corrispondenza - quindi si ferma. Per esempio:

using (var iterator = query.GetEnumerator()) 
{ 
    iterator.MoveNext(); // This will look for the first match 
    Console.WriteLine(iterator.Current); 

    iterator.MoveNext(); // This will continue from just after the first match 
} 

Ognuna delle operazioni funziona in questo modo - quindi per il momento che hai:

var query = list.Where(...) 
       .Select(...) 
       .Where(...); 

... quando si chiede il primo elemento all'interno query, lo farà a catena backup (quindi l'ultimo Where chiederà il risultato di Select, che chiederà il risultato del primo Where, che chiederà l'elenco) e continuerà fino a quando non avrà un risultato. Poi, quando si chiede la voce successiva, che chiederà il risultato della Select per la voce successiva, ecc

ToList costruisce un List<T> da tutti gli elementi nella sua fonte, subito - è desiderosi in tal senso (piuttosto che gli altri operatori che sono pigri). Ma la stessa lista originale verrà ripetuta solo una volta.

Per un molto maggiori dettagli su come LINQ to Objects opere - tra cui un esempio di implementazione - si potrebbe desiderare di leggere il mio Edulinq blog series.

8

list sarebbe iterato solo una volta in quel codice, non 3 volte.

Naturalmente, se si desidera verificare se qualsiasi richiesta arbitraria itera la fonte più volte è abbastanza facile per testare sperimentalmente, basta creare un IEnumerable che genera un'eccezione quando si tenta di iterare che più volte:

public static IEnumerable<T> ThereCanBeOnlyOne<T>(this IEnumerable<T> source) 
{ 
    return new SingleEnumerable<T>(source); 
} 

private class SingleEnumerable<T> : IEnumerable<T> 
{ 
    private bool hasRun = false; 
    private IEnumerable<T> wrapped; 
    public SingleEnumerable(IEnumerable<T> wrapped) 
    { 
     this.wrapped = wrapped; 
    } 
    public IEnumerator<T> GetEnumerator() 
    { 
     if (hasRun) 
      throw new InvalidOperationException(
       "Sequence cannot be enumerated multilpe times"); 
     else 
     { 
      hasRun = true; 
      return wrapped.GetEnumerator(); 
     } 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return GetEnumerator(); 
    } 
} 

Ora si può semplicemente scrivere:

list.ThereCanBeOnlyOne() 
    .Where(p=>p.Number > n) 
    .Select(p=> p.Name) 
    .Where(n=> n.StartsWith(a)) 
    .ToList(); 

Se il codice genera un'eccezione, si è tentato di iterare l'elenco sottostante più volte. Se no, non l'hai fatto.

Problemi correlati