web-dev-qa-db-ja.com

C#でループするためにIEnumeratorを使用する必要があるのはいつですか?

コレクションを反復処理するためにforeachループではなくIEnumeratorを使用することが有利な場合があるかどうか疑問に思いました。たとえば、次のコードサンプルのいずれかを他のサンプルよりも使用したほうがよい場合はありますか?

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

の代わりに

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

まず、例(foreachGetEnumeratorの間)の大きな違いの1つは、イテレーターがイテレーターの場合、foreachがイテレーターでDispose()を呼び出すことを保証することです。はIDisposableです。これは、多くのイテレータにとって重要です(たとえば、外部データフィードを消費している可能性があります)。

実際、foreachが私たちが望むほど役に立たない場合があります。

まず、「最初の項目」のケースについて説明します ここでは(foreach /最初の反復の検出)

しかし、より多くの; 2つの列挙可能なシーケンスをつなぎ合わせるために欠落しているZipメソッド(またはSequenceEqualメソッド)を書き込もうとすると、両方のシーケンスでforeachを使用できることがわかります。クロスジョインを実行します。それらの1つに直接イテレータを使用する必要があります。

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            
}
31
Marc Gravell

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

2つの例の本質は同じですが、いくつかの重要な違いがあります。特に、try/finallyです。

19
Jay Bazuzi

C#では、foreachは上記で投稿したIEnumeratorコードとまったく同じことをしています。 foreach構造は、プログラマーが簡単に使用できるように提供されています。どちらの場合も生成されるILは同じ/類似していると思います。

12
Andy

最初の反復が他とは異なることをするときに、私は時々それを使用しました。

たとえば、メンバーをコンソールに出力し、結果を行ごとに区切る場合は、次のように記述できます。

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を使用できます