2010-02-12 6 views
32

Mi manca qualcosa di semplice qui.Utilizzo di IEnumerable senza ciclo foreach

Prendere il seguente codice:

public IEnumerable<int> getInt(){ 
    for(int i = 0; i < 10; i++){ 
    yield return i; 
    } 
} 

posso chiamare questo con:

foreach (int j in obj.getInt()){ 
    //do something with j 
} 

Come posso utilizzare il metodo getInt senza il ciclo foreach:

IEnumerable<int> iter = obj.getInt(); 
// do something with iter ?? 

Grazie.

modifiche

Per coloro che chiedono il motivo per cui avrei voluto questo. Sto iterando due cose:

IEnumerator<int> iter = obj.getInt().GetEnumerator(); 
foreach(object x in xs){ 
    if (x.someCondition) continue; 
    iter.MoveNext(); 
    int n = iter.current(); 
    x.someProp = n; 
    etc... 
} 
+0

Che cosa stai cercando di fare esattamente? L'intero punto in "IEnumerable " è di consentire l'iterazione e un ciclo 'foreach' è il modo più efficiente per farlo. –

+0

@Zakalwe, voglio ripetere due enumerabili contemporaneamente. Il foreach si sta muovendo attraverso il primo, se incontra una condizione, ho bisogno del prossimo int (solo un esempio, non proprio un int). – Mark

risposta

52

È possibile ottenere un riferimento al Enumerator, utilizzando il metodo GetEnumerator, quindi è possibile utilizzare il metodo MoveNext() di andare avanti, e utilizzare la proprietà Current di accedere ai vostri elementi:

var enumerator = getInt().GetEnumerator(); 
while(enumerator.MoveNext()) 
{ 
    int n = enumerator.Current; 
    Console.WriteLine(n); 
} 
+1

Giusto. Ma quale beneficio ottieni usando questo invece? –

+0

Permette di eseguire elaborazioni speciali su, ad esempio, i primi elementi, o di fermarsi interamente dopo l'elaborazione di molti, ecc. Fondamentalmente ti dà un po 'più di versatilità. –

+3

@Anon: puoi fare lo stesso con 'foreach'. –

3

Che ne dici di questo?

IEnumerator<int> iter = obj.getInt(); 
using(iter) { 
    while(iter.MoveNext()) { 
     DoSomethingWith(iter.Current) 
    } 
} 
+0

@itowlson, era esattamente quello che mi mancava! – Mark

29

mio consiglio: non scherzare affatto con gli enumeratori. Caratterizza il tuo problema come una serie di operazioni su sequenze. Scrivi il codice per esprimere quelle operazioni. Lascia che gli operatori di sequenza si occupino della gestione degli enumeratori.

Quindi vediamo se ho capito bene. Hai due sequenze. Diciamo {2, 3, 5, 7, 12} e {"rana", "rospo"}. L'operazione logica che si desidera eseguire è, ad esempio, "passare attraverso la prima sequenza: ogni volta che si trova un numero divisibile per tre, ottenere l'elemento successivo nella seconda sequenza. Fare qualcosa con la coppia risultante (numero, anfibio)".

Facilmente fatto. In primo luogo, filtrare la prima sequenza:

var filtered = firstSequence.Where(x=>x%3 == 0); 

Avanti, zip la sequenza filtrata con la seconda sequenza:

var zipped = filtered.Zip(
      secondSequence, 
      (y, z)=> new {Number = x, Amphibian = y}); 

E ora si può iterare la sequenza con zip e fare quello che vuoi con le coppie:

foreach(var pair in zipped) 
    Console.WriteLine("{0} : {1}", pair.Number, pair.Amphibian); 

Facile, non scherzare con gli enumeratori.

+0

@Eric, grazie, mi piace, questo è estremamente vicino a ciò di cui ho bisogno. Trascorro più tempo a programmare in python ed è molto simile a come lo farei lì (ho bisogno di studiare le funzionalità di C#). Sono curioso però, quali sono le potenziali insidie ​​degli enumeratori? – Mark

+5

@ Marco: (1) la gente dimentica di disporli. (2) Gli enumeratori enfatizzano * come * il codice funziona piuttosto che * a cosa serve * - leggere il codice con gli enumeratori è come guardare una descrizione di un motore di trapano quando quello che si vuole descrivere è il foro risultante.A volte sono necessarie, ma preferirei esprimere le mie operazioni a un livello di astrazione più alto di quello in cui devo tenere traccia esplicitamente di più "cursori" in più sequenze. –

+3

@Eric, un avvertimento al tuo approccio è che se questo sta agendo su un grande volume di dati in memoria, potrebbe esserci un guadagno in termini di prestazioni se iterando manualmente i due elenchi contemporaneamente ... Se si tratta di una piccola quantità di dati o prestazioni non è una preoccupazione quindi scelgo il codice elegante (cioè il tuo) sul codice nitty grintoso. –

0

Sebbene le risposte accettate siano corrette, notare che IEnumerator.Current non è definito prima della prima chiamata a MoveNext().

Se si effettua l'iterazione di una serie secondaria, si vorrà qualcosa di simile:

IEnumerable<Foo> Foo() { ... } 

int AssignValues(List<TakesFoo> data) { 
    var count = 0; 
    var foo = Foo().GetEnumerator(); 

    // Step into the first element of the array; 
    // doing this does not discard a value from the IEnumerator 
    if (foo.MoveNext()) { 
    foreach (var x in data) { 
     x.SetFoo(foo.Current); 
     count += 1; 
     if (!foo.MoveNext()) { 
     break; 
     } 
    } 

    // Return count of assigned values 
    return count; 
} 
2

utilizzando ciclo for:

for (var enumerator = getInt().GetEnumerator(); enumerator.MoveNext();) 
{ 
    Console.WriteLine(enumerator.Current); 
} 
+0

È necessario disporre l'enumeratore alla fine – CharlesB

0

E 'importante ricordare che il dovere del ciclo foreach è per smaltire l'enumeratore se l'istanza implementa IDisposable. In altre parole, foreach deve essere sostituito con qualcosa del tipo:

var enumerator = enumerable.GetEnumerator(); 

try 
{ 
    while (enumerator.MoveNext()) 
    { 
     var item = enumerator.Current; 
     // do stuff 
    } 
} 
finally 
{ 
    var disposable = enumerator as IDisposable; 
    if (disposable != null) 
    { 
     disposable.Dispose(); 
    } 
}