2011-09-30 13 views
9

Diciamo che ho un po 'di codice:Leggendo un IEnumerable più volte

var items = ItemsGetter.GetAllItems().Where(x => x.SomeProperty > 20); 
int sum1 = items.Sum(x => x.SomeFlag == true); 

E per esempio, ho bisogno di qualche altra somma della collezione oggetti più avanti nel codice.

int sum2 = items.Sum(x => x.OtherFlag == false); 

Quindi la mia domanda: E 'OK per chiamare i metodi Linq su IEnumerable più di una volta? Forse dovrei chiamare il metodo Reset() sull'enumeratore o creare una lista dagli elementi usando il metodo ToList?

risposta

16

Beh, dipende davvero da cosa si vuole fare. Si potrebbe prendere il colpo di eseguire la query due volte (e il significato esatto di che dipenderà da quello GetAllItems() fa), o si poteva prendere il colpo di copiare i risultati in un elenco:

var items = ItemsGetter.GetAllItems().Where(x => x.SomeProperty > 20).ToList(); 

Una volta che è in una lista, ovviamente, non è un problema ripetere questa lista più volte.

Nota che non è possibile chiamare Reset perché non si ha l'iteratore: si dispone dello IEnumerable<T>. Non raccomanderei comunque di chiamare lo IEnumerator<T> in generale - molte implementazioni (comprese quelle generate dal compilatore C# dai blocchi iteratori) non implementano in realtà lo Reset (cioè generano un'eccezione).

1

LINQ utilizza l'esecuzione posticipata, quindi "gli elementi" verranno enumerati solo quando lo si richiede tramite un altro metodo. Ciascuno dei tuoi metodi Sum richiederà O (n) per scorrere. A seconda di quanto è grande la tua lista di articoli, potresti non voler iterarlo più volte.

+0

Questo non è un rispondere alla domanda, che è stata "È corretto chiamare i metodi Linq su IEnumerable più di una volta?" ... la risposta è no". –

+0

Bene, quello che "OK" significa è un po 'poco chiaro. Puoi scorrere su una lista tutte le volte che vuoi. Cito le ramificazioni di questo nella mia risposta. Come non va bene iterare su di loro? Pubblica la tua risposta se la pensi così (anche se c'è già una risposta accettata). –

+0

È perfettamente chiaro. 'items' è un oggetto IEnumerable, non un elenco e puoi attraversarlo solo una volta. Non è necessario pubblicare un'altra risposta, ma è importante che i lettori capiscano che questo è sbagliato e non tocca nemmeno la domanda. –

3

Sono occasionalmente nella situazione che devo elaborare un numero enumerabile più volte. Se l'enumerazione è costosa, non ripetibile e genera molti dati (come un IQueryable che legge da un database), l'enumerazione di più volte non è un'opzione, né la memorizzazione dei risultati avviene in memoria.

Fino a oggi ho finito spesso a scrivere classi di aggregatori in cui potevo inserire elementi in un ciclo foreach e alla fine leggere i risultati - molto meno elegante di LINQ.

Ma aspetta, ho appena detto "push"? Non suona come ... reattivo? Quindi stavo pensando durante la passeggiata di stanotte. A casa ho provato - e funziona!

Il frammento di esempio mostra come ottenere sia il numero massimo di voci minimo e da una sequenza di numeri interi in un singolo passaggio, utilizzando gli operatori LINQ normali (quelli di Rx, cioè):

public static MinMax GetMinMax(IEnumerable<int> source) 
{ 
    // convert source to an observable that does not enumerate (yet) when subscribed to 
    var connectable = source.ToObservable(Scheduler.Immediate).Publish(); 

    // set up multiple consumers 
    var minimum = connectable.Min(); 
    var maximum = connectable.Max(); 

    // combine into final result 
    var final = minimum.CombineLatest(maximum, (min, max) => new MinMax { Min = min, Max = max }); 

    // make final subscribe to consumers, which in turn subscribe to the connectable observable 
    var resultAsync = final.GetAwaiter(); 

    // now that everybody is listening, enumerate! 
    connectable.Connect(); 

    // result available now 
    return resultAsync.GetResult(); 
}