2009-01-19 6 views
23

Ich frage mich, ob es Zeiten gibt, in denen es vorteilhaft ist, einen IEnumerator über eine Foreach-Schleife zu verwenden, um durch eine Sammlung zu iterieren? Gibt es beispielsweise eine Zeit, in der es besser wäre, eines der folgenden Codebeispiele gegenüber dem anderen zu verwenden?Wann sollte ich IEnumerator zum Einschleifen von C# verwenden?

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

statt

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

Antwort

30

Zuerst beachten Sie, dass ein großer Unterschied in Ihrem Beispiel (zwischen foreach und GetEnumerator) ist, dass foreach Garantien Dispose() auf dem Iterator anrufen verwenden, wenn der Iterator IDisposable ist. Dies ist wichtig für viele Iteratoren (die z. B. einen externen Daten-Feed benötigen).

Tatsächlich gibt es Fälle, in denen foreach nicht so hilfreich ist, wie wir möchten.

Zuerst ist der Fall "erster Artikel" here (foreach/detecting first iteration) diskutiert.

Aber mehr; Wenn Sie versuchen, die fehlende Zip Methode zum Zusammenfügen von zwei aufzählbaren Sequenzen (oder die SequenceEqual Methode) zu schreiben, finden Sie, dass Sie foreach für beide Sequenzen verwenden können, da dies eine Kreuzverbindung durchführen würde. Sie müssen den Iterator direkt für einen von ihnen verwenden:

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

Sollte es nicht so sein wie while (iter.MoveNext()) falls right zwei oder mehr Elemente als left hat? – Spoike

+0

Zip? Ja ... oops (fixed) - danke kindly –

+0

Hallo Marc, sehr gute Antwort! Wollen Sie näher erläutern, was Sie unter "Cross-Join" verstehen? –

12

In C# foreach tut genau das Gleiche wie die IEnumerator Code, den Sie oben gepostet. Das foreach-Konstrukt ist vorgesehen, um es für den Programmierer einfacher zu machen. Ich glaube, dass die IL in beiden Fällen gleich/ähnlich ist.

+0

+1 Die IL ist identisch. –

+1

Es gibt Fälle, wo Sie es manuell tun müssen - siehe meine Antwort unten. –

+3

Eigentlich ist die IL in ihrer Stichprobe ** nicht ** identisch, weil sie den Iterator für IDisposable nicht überprüft hat. Dies ist wichtig für viele Iteratoren. –

19

Nach der C# language spec:

A foreach Anweisung der Form

foreach (V v in x) embedded-statement 

expandiert dann:

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

Der Sinn Ihrer beiden Beispiele ist der gleiche, aber es gibt einige wichtige Unterschiede, vor allem die try/finally.

+1

+1 für die Bezugnahme auf die Sprachspezifikation. –

+1

Interessant, da dies auch zeigt, welche Art von impliziten Konvertierungen (optional benutzerdefiniert) ausgelöst werden kann. – sehe

4

Ich habe es manchmal verwendet, wenn die erste Iteration etwas anderes als andere tun.

Wenn Sie beispielsweise Mitglieder für die Konsole drucken und Ergebnisse nach Zeilen trennen möchten, können Sie schreiben.

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

Dann ist das Ergebnis

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

Und eine andere Situation ist, wenn ich zusammen 2 Enumeratoren iterieren.

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); 
    } 
} 

Ergebnis

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

enumerator ermöglicht es Ihnen, flexibler auf Ihre Iteration zu tun. Aber in den meisten Fällen können Sie foreach