2012-02-10 12 views
8

Stavo rifacendo un po 'di codice in precedenza e mi sono imbattuto in un'implementazione di un blocco iteratore di cui non ero molto sicuro. In un livello di integrazione di un sistema in cui il client chiama un'API esterna per alcuni dati, ho una serie di traduttori che prendono i dati restituiti dall'API e li traducono in raccolte di entità aziendali utilizzate nel livello logico. Una classe di traduttore comune sarà simile a questa:Uso corretto dei blocchi iteratori

// translate a collection of entities coming back from an extrernal source into business entities 
public static IEnumerable<MyBusinessEnt> Translate(IEnumerable<My3rdPartyEnt> ents) { 

    // for each 3rd party ent, create business ent and return collection 
    return from ent in ents 
      select new MyBusinessEnt { 
       Id = ent.Id, 
       Code = ent.Code 
      }; 
} 

Oggi mi sono imbattuto nel seguente codice. Ancora una volta, è una classe di traduttore, il suo scopo è tradurre la raccolta nel parametro nel tipo di ritorno del metodo. Tuttavia, questa volta si tratta di un blocco iteratore:

// same implementation of a translator but as an iterator block 
public static IEnumerable<MyBusinessEnt> Translate(IEnumerable<My3rdPartyEnt> ents) { 
    foreach(var ent in ents) 
    { 
     yield return new MyBusinessEnt { 
      Id = ent.Id, 
      Code = ent.Code 
     }; 
    } 
} 

La mia domanda è: È questo un valido utilizzo di un blocco iteratore? Non riesco a vedere il vantaggio di creare una classe di traduttore in questo modo. Ciò potrebbe comportare un comportamento inaspettato?

+0

Sembra perfettamente valido per me- fornisce una traduzione sicura in fase di compilazione tra due entità.Qual è il problema con esso? –

risposta

13

I tuoi due campioni fanno praticamente la stessa cosa. La versione della query verrà riscritta in una chiamata a Seleziona e Select verrà scritta esattamente come il tuo secondo esempio; itera su ogni elemento nella collezione di origine e yield-restituisce un elemento trasformato.

Questo è un uso perfettamente valido di un blocco iteratore, anche se naturalmente non è più necessaria di scrivere i propri blocchi iteratore come questo, perché si può semplicemente utilizzare Select.

+0

geniale - grazie per i chiarimenti. –

3

Sì, è valido. Il foreach ha il vantaggio di essere debuggabile, quindi tendo a preferire questo design.

+0

Quindi il blocco iteratore potrebbe causare confusione a chiunque provi a eseguire il debug di un foreach su questa raccolta di entità aziendali più avanti nel sistema? –

+1

Non penso che ci sia necessariamente alcuna confusione; il blocco 'foreach' semplifica il targeting degli elementi pubblicitari tradotti. Ciò consente un debug più semplice se la traduzione diventa sempre più banale. Entrambe sono perfettamente in grado di trovare implementazioni. – eouw0o83hf

3

Il primo esempio non è un iteratore. Crea e restituisce semplicemente un IEnumerable<MyBusinessEnt>.

Il secondo è un iteratore e non vedo nulla di sbagliato in esso. Ogni volta che il chiamante itera sopra il valore di ritorno di quel metodo, lo yield restituirà un nuovo elemento.

-1

La differenza è quando viene eseguito ogni codice. Il primo viene ritardato finché il valore di ritorno non viene iterato mentre il secondo viene eseguito immediatamente. Quello che voglio dire è che il per il ciclo sta forzando l'iterazione per l'esecuzione. Il fatto che la classe esponga un IEnumerable<T> e in questo caso sia ritardata è un'altra cosa.

Questo non fornisce alcun vantaggio rispetto al semplice Select. vero potere yield s' è quando c'è coinvolto un condizionale:

foreach(var ent in ents) 
{ 
    if(someCondition) 
    yield return new MyBusinessEnt { 
     Id = ent.Id, 
     Code = ent.Code 
    }; 
} 
+6

Tieni duro per un minuto - la tua versione non offre alcun vantaggio su un semplice * Dove * seguito da * Seleziona *. E sì, il commento di Chris Shouts è corretto; entrambi rinviano l'esecuzione. –

+0

@EricLippert yes poiché utilizza un ciclo e non seleziona. Ma come ho detto, in questo caso userei semplicemente Linq. – Aliostad

+0

Sei sicuro di questo? Ho pensato che entrambi hanno creato i loro valori su richiesta in modo efficace - cioè il secondo non viene eseguito immediatamente. – Chris

3

Sì, che funziona bene, e il risultato è molto simile.

Entrambi crea un oggetto che è in grado di restituire il risultato. Entrambi si basano sulla sorgente enumerabile per rimanere intatti fino a quando il risultato non è completato (o abbreviato). Entrambi utilizzano l'esecuzione posticipata, ovvero gli oggetti vengono creati uno alla volta quando si itera il risultato.

Esiste una differenza in quanto il primo restituisce un'espressione che utilizza i metodi di libreria per produrre un enumeratore, mentre il secondo crea un enumeratore personalizzato.

+0

OK, ma non è necessario creare un iteratore personalizzato in questo esempio, giusto? Come sottolineato da @Aliostad, i blocchi iteratori vengono utilizzati al meglio con un condizionale. Quindi forse il secondo metodo è leggermente meno leggibile \ debuggable e quindi forse il primo approccio sarebbe più adatto a questo scenario. –

+0

Anche la differenza è che l'enumeratore personalizzato viene eseguito immediatamente mentre il primo ('select this from that') è in ritardo. – Aliostad

+0

@Aliostad - che, non capisco ... quindi il primo esempio restituisce una collezione che non è stata ancora enumerata. Il secondo sta enumerando la raccolta di ritorno ogni volta che viene chiamato? È corretto? –