2010-11-12 11 views
5

Sto cercando di comprendere cosa fa il compilatore C# quando sto concatenando i metodi linq, in particolare quando concatenando lo stesso metodo più volte.Comprendere come il compilatore C# si occupa di concatenare i metodi linq

Esempio semplice: Diciamo che sto provando a filtrare una sequenza di ints in base a due condizioni.

La cosa più ovvia da fare è qualcosa di simile:

IEnumerable<int> Method1(IEnumerable<int> input) 
{ 
    return input.Where(i => i % 3 == 0 && i % 5 == 0); 
} 

Ma noi potrebbe concatenare anche le modalità in cui, con una sola condizione in ciascuna:

IEnumerable<int> Method2(IEnumerable<int> input) 
{ 
    return input.Where(i => i % 3 == 0).Where(i => i % 5 == 0); 
} 

Ho avuto un guarda l'IL in Reflector; è ovviamente diverso per i due metodi, ma analizzando ulteriormente va oltre la mia conoscenza in questo momento :)

vorrei sapere:
a) ciò che il compilatore fa in modo diverso in ogni caso, e perché.
b) Nessuna implicazioni sulle prestazioni (non cercando di micro-ottimizzare;! Solo curioso)

risposta

9

La risposta a (a) è breve, ma andrò più in dettaglio qui di seguito:

Il compilatore in realtà non fare il concatenamento - accade in fase di esecuzione, attraverso il normale organizzazione degli oggetti! C'è molto meno magia qui di quello che potrebbe sembrare a prima vista - Jon Skeet recently completed the "Where clause" step nella sua serie di blog, Re-implementazione di LINQ to Objects. Consiglierei di leggerlo.

In termini molto brevi, quello che succede è questo: ogni volta che si chiama il metodo di estensione Where, restituisce un nuovo WhereEnumerable oggetto che ha due cose - un riferimento al precedente IEnumerable (quella che si chiama Where via), e il lambda che hai fornito.

Quando si avvia l'iterazione di questo WhereEnumerable (ad esempio, in un foreach successivamente verso il basso nel codice), internamente inizia semplicemente iterazione sulla IEnumerable che si ha fatto riferimento.

"Questo foreach appena mi ha chiesto per l'elemento successivo nella mia sequenza, quindi sto girando intorno e si chiede per l'elemento successivo nella sequenza".

Questo va fino in fondo alla catena fino a quando non si preme l'origine, che in realtà è una sorta di array o di memorizzazione di elementi reali.Come ogni Enumerable dice "OK, ecco il mio elemento" passandogli il backup della catena, applica anche la sua logica personalizzata. Per un Where, applica il lambda per vedere se l'elemento supera i criteri. In tal caso, consente di continuare al prossimo chiamante. Se fallisce, si ferma a quel punto, torna al suo Enumerable referenziato e chiede l'elemento successivo.

Ciò continua a verificarsi finché tutti gli MoveNext di tutti restituiscono false, il che significa che l'enumerazione è completa e non ci sono più elementi.

Per rispondere (b), c'è sempre la differenza, ma qui è troppo banale per perdere tempo con. Non preoccuparti :)

+0

Bella risposta. Abbiamo bisogno di più materiale come questo su Stackoverflow. –

1
  1. Il primo utilizzerà un iteratore, il secondo userà due. Cioè, il primo imposta un oleodotto con uno stadio, il secondo prevede due fasi.

  2. Due iteratori hanno uno svantaggio di prestazione leggero a uno.

Problemi correlati