Подтвердить что ты не робот

Когда следует использовать IEnumerator для цикла в С#?

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

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

вместо

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

Ответ 1

Во-первых, обратите внимание, что одна большая разница в вашем примере (между 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            
}

Ответ 2

В соответствии с спецификацией языка С#:

A для любой формулировки формы

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.

Ответ 3

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

Ответ 4

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

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

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