2009-01-19 10 views
23

Me preguntaba si hay momentos en los que sea ventajoso usar un IEnumerator en un bucle foreach para iterar a través de una colección. Por ejemplo, ¿hay algún momento en el que sea mejor usar cualquiera de los siguientes ejemplos de código sobre el otro?¿Cuándo debería usar IEnumerator para el bucle en C#?

IEnumerator<MyClass> classesEnum = myClasses.GetEnumerator(); 
while(classesEnum.MoveNext()) 
    Console.WriteLine(classesEnum.Current); 

en lugar de

foreach (var class in myClasses) 
    Console.WriteLine(class); 

Respuesta

30

En primer lugar, tenga en cuenta que una gran diferencia en su ejemplo (entre foreach y GetEnumerator) es que foreach garantías para llamar Dispose() en el iterador si el iterador es IDisposable. Esto es importante para muchos iteradores (que podrían estar consumiendo un feed de datos externo, por ejemplo).

En realidad, hay casos en que foreach no es tan útil como nos gustaría.

En primer lugar, se trata el caso del "primer elemento" discutido here (foreach/detecting first iteration).

Pero más; Si intenta escribir el método Zip que falta para unir dos secuencias enumerables juntas (o el método SequenceEqual), encontrará que puede 'usar foreach en ambas secuencias, ya que eso llevaría a cabo una combinación cruzada. Necesita usar el iterador directamente para uno de ellos:

static IEnumerable<T> Zip<T>(this IEnumerable<T> left, 
    IEnumerable<T> right) 
{ 
    using (var iter = right.GetEnumerator()) 
    { 
     // consume everything in the first sequence 
     foreach (var item in left) 
     { 
      yield return item; 

      // and add an item from the second sequnce each time (if we can) 
      if (iter.MoveNext()) 
      { 
       yield return iter.Current; 
      } 
     } 
     // any remaining items in the second sequence 
     while (iter.MoveNext()) 
     { 
      yield return iter.Current; 
     }     
    }    
} 

static bool SequenceEqual<T>(this IEnumerable<T> left, 
    IEnumerable<T> right) 
{ 
    var comparer = EqualityComparer<T>.Default; 

    using (var iter = right.GetEnumerator()) 
    { 
     foreach (var item in left) 
     { 
      if (!iter.MoveNext()) return false; // first is longer 
      if (!comparer.Equals(item, iter.Current)) 
       return false; // item different 
     } 
     if (iter.MoveNext()) return false; // second is longer 
    } 
    return true; // same length, all equal    
} 
+0

¿No debería ser algo así como while (iter.MoveNext()) en caso de que el derecho tenga dos o más elementos que los que quedan? – Spoike

+0

Zip? Sí ... oops (fijo) - gracias amablemente –

+0

Hola Marc, ¡muy buena respuesta! ¿Podrías explicar lo que quieres decir con "realizar una combinación cruzada"? –

12

En C#, foreach está haciendo exactamente lo mismo que el código IEnumerator que envió por encima. La construcción foreach se proporciona para que sea más fácil para el programador. Creo que el IL generado en ambos casos es el mismo/similar.

+0

+1 El IL es idéntico. –

+1

Hay casos en los que debe hacerlo manualmente; consulte mi respuesta a continuación. –

+3

En realidad, no el IL es ** no ** idéntico en su muestra, porque no ha verificado el iterador para IDisposable. Esto es importante para muchos iteradores. –

19

De acuerdo con la C# language spec:

Una instrucción foreach de la forma

foreach (V v in x) embedded-statement 

se expande a continuación:

{ 
    E e = ((C)(x)).GetEnumerator(); 
    try { 
      V v; 
      while (e.MoveNext()) { 
        v = (V)(T)e.Current; 
        embedded-statement 
      } 
    } 
    finally { 
      ... // Dispose e 
    } 
} 

La esencia de sus dos ejemplos es la misma, pero hay algunas diferencias importantes, sobre todo el try/finally.

+1

+1 para referirse a la especificación del idioma. –

+1

Interesante porque esto también muestra qué tipo de conversiones implícitas (opcionalmente definidas por el usuario) pueden activarse – sehe

4

Lo usé a veces cuando la primera iteración hace algo diferente de los demás.

Por ejemplo, si desea imprimir miembros a la consola y separar los resultados por línea, puede escribir.

using (IEnumerator<MyClass> classesEnum = myClasses.GetEnumerator()) { 
    if (classEnum.MoveNext()) 
     Console.WriteLine(classesEnum.Current); 
    while (classesEnum.MoveNext()) { 
     Console.WriteLine("-----------------"); 
     Console.WriteLine(classesEnum.Current); 
    } 
} 

entonces el resultado es

myClass 1 
----------------- 
myClass 2 
----------------- 
myClass 3 

Y otra situación es cuando iterar 2 encuestadores juntos.

using (IEnumerator<MyClass> classesEnum = myClasses.GetEnumerator()) { 
    using (IEnumerator<MyClass> classesEnum2 = myClasses2.GetEnumerator()) { 
     while (classesEnum.MoveNext() && classEnum2.MoveNext()) 
      Console.WriteLine("{0}, {1}", classesEnum.Current, classesEnum2.Current); 
    } 
} 

resultado

myClass 1, myClass A 
myClass 2, myClass B 
myClass 3, myClass C 

Usando encuestador le permite hacer más flexible en su iteración. Sin embargo, en la mayoría de casos, se puede usar foreach

Cuestiones relacionadas