24

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

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

それ以外の

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

4 に答える 4

31

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

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

まず、ここで説明する「最初の項目」のケースがあります (foreach / 最初の反復の検出)

しかし、より多くの; Zip2 つの列挙可能なシーケンスをつなぎ合わせる欠落しているメソッド (またはメソッド) を記述しようとすると、クロス結合が実行されるため、両方のシーケンスで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            
}
于 2009-01-19T05:07:43.197 に答える
19

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です。

于 2009-01-19T04:01:22.907 に答える
12

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

于 2009-01-19T03:20:53.833 に答える
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

もう 1 つの状況は、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 を使用できます

于 2009-01-19T04:42:35.617 に答える