Когда я должен использовать IEnumerator для цикла в C #? - PullRequest
24 голосов
/ 19 января 2009

Мне было интересно, есть ли случаи, когда выгодно использовать IEnumerator вместо цикла foreach для перебора коллекции? Например, есть ли время, когда было бы лучше использовать один из следующих примеров кода поверх другого?

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

вместо

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

Ответы [ 4 ]

31 голосов
/ 19 января 2009

Во-первых, обратите внимание, что одно большое различие в вашем примере (между foreach и GetEnumerator) состоит в том, что foreach гарантирует вызов Dispose() на итераторе, если итератор равен IDisposable. Это важно для многих итераторов (которые могут потреблять, например, внешний поток данных).

На самом деле, бывают случаи, когда foreach не так полезен, как хотелось бы.

Во-первых, здесь обсуждается случай "первого элемента" (foreach / обнаружение первой итерации) .

Но больше; если вы попытаетесь написать отсутствующий метод Zip для сшивания двух перечислимых последовательностей (или метод SequenceEqual), вы обнаружите, что вы можете «использовать foreach для обеих последовательностей, поскольку это приведет к перекрестному соединению». Вам нужно использовать итератор непосредственно для одного из них:

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            
}
19 голосов
/ 19 января 2009

Согласно спецификации языка C # :

оператор foreach вида

foreach (V v in x) embedded-statement

затем расширяется до:

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

Суть ваших двух примеров одинакова, но есть некоторые важные различия, в частности try / finally.

12 голосов
/ 19 января 2009

В C # foreach делает то же самое, что и код IEnumerator, который вы разместили выше. Конструкция foreach облегчает работу программиста. Я считаю, что ИЛ, вырабатываемый в обоих случаях, одинаков / похож.

4 голосов
/ 19 января 2009

Я использовал его иногда, когда первая итерация делает что-то отличное от других.

Например, если вы хотите печатать элементы на консоли и разделять результаты по строкам, вы можете написать.

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

Тогда результат

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

И еще одна ситуация, когда я перебираю 2 счетчика вместе.

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

результат

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

Использование перечислителя позволяет сделать вашу итерацию более гибкой. Но в большинстве случаев вы можете использовать foreach

...