8

私は基本的に、クラスを使用して反復できるようにしようとしていforeachます。このチュートリアルを読みました。MSDN . それは非常に簡単に思えます。しかし、2回目の繰り返しをしたいときに問題があります。私はそれをデバッグしました。Reset()関数を呼び出していないことがわかりました。

クラスA

class A : IEnumerable, IEnumerator
{
    int[] data = { 0, 1, 2, 3, 4 };

    int position = -1;

    public object Current
    {
        get
        {
            return data[position];
        }
    }

    public bool MoveNext()
    {
        position++;
        return (position < data.Length);
    }

    public void Reset()
    {
        position = -1;
    }

    public IEnumerator GetEnumerator()
    {
        return (IEnumerator)this;
    }
}

次のメイン関数を実行すると; 関数を呼び出すことはありませんReset()。したがって、1回ループした後、クラスを再度反復することはできません。

主要

static void Main(string[] args)
{
    A a = new A();

    foreach (var item in a)
    {
        Console.WriteLine(item);
    }

    Console.WriteLine("--- First foreach finished. ---");

    foreach (var item in a)
    {
        Console.WriteLine(item);
    }
}

出力:

0
1
2
3
4
--- First foreach finished. ---
Press any key to continue . . .

何かご意見は?

4

4 に答える 4

16

が呼び出されるたびに、新しいforeachを要求します。クラス インスタンスを返すのは悪い考えです。別のクラスを作成して を実装し、代わりにそれを返す必要があります。 IEnumeratorIEnumerator

これは、ネストされた (プライベート) クラスを使用し、そのインスタンスを返すことによって行われることがよくあります。クラス A のインスタンスをプライベート クラスに渡し (へのアクセスを許可data)、positionそのクラスにフィールドを配置できます。複数の列挙子を同時に作成できるようになり、後続の foreach 呼び出しで適切に機能します。

たとえば、コードを変更するには、次のようにします。

using System;
using System.Collections;

class A : IEnumerable
{
    int[] data = { 0, 1, 2, 3, 4 };

    public IEnumerator GetEnumerator()
    {
        return new AEnumerator(this);
    }

    private class AEnumerator : IEnumerator
    {
        public AEnumerator(A inst)
        {
            this.instance = inst;
        }

        private A instance;
        private int position = -1;

        public object Current
        {
            get
            {
                return instance.data[position];
            }
        }

        public bool MoveNext()
        {
            position++;
            return (position < instance.data.Length);
        }

        public void Reset()
        {
            position = -1;
        }

    }
}

配列の列挙子を直接返すこともできることに注意してください (きれいな列挙子を作成する方法を学ぼうとしていると仮定していましたが):

class A : IEnumerable
{
    int[] data = { 0, 1, 2, 3, 4 };

    public IEnumerator GetEnumerator()
    {
        return data.GetEnumerator();
    }
}

最後に、イテレータを使用して、これをはるかに簡単な方法で実装できます。

class A : IEnumerable
{
    int[] data = { 0, 1, 2, 3, 4 };

    public IEnumerator GetEnumerator()
    {
        for (int i=0;i<data.Length;++i)
           yield return data[i];
    }
}

そうは言っても、IEnumerable<int>IEnumerable に加えて実装することを強くお勧めします。ジェネリックは、使用に関してこれをはるかに優れたものにします。

于 2012-07-13T17:24:16.230 に答える
6

Reset() は基本的に間違いでした。可能であればクリーンな列挙子を取得する既知のメソッドが既にあります: GetEnumerator()。

イテレータ ブロックの実装 (イテレータ用) がこのメソッドに対して例外をスローすることは、仕様の要件です。したがって、一般的なケースでは、動作が期待できないことが正式に知られています。単純に、誰もそれを呼び出しません。率直に言って、API で [Obsolete] とマークするべきでした。

さらに、多くのシーケンスは再現できません。NetworkStream または乱数ジェネレーターに置かれたイテレーターを考えてみてください。イテレータ (一般的なケース) は反復可能である必要はないため、可能な場合は、反復子を最大 1 回反復することを目指してください。それが不可能な場合は、おそらく ToList() によるバッファリングです。

「foreach」はどの時点でも Reset() を含みません。GetEnumerator()、MoveNext()、Current、Dispose() だけです。

于 2012-07-13T17:26:58.500 に答える
4

列挙は を呼び出しませんReset。列挙子の新しいインスタンスを作成する必要があります。これは、次のコードのように、列挙子に別のクラスを作成することを意味する可能性があります (つまり、IEnumerable と IEnumerator に同じ型を使用しない)。

public class StackOverflow_11475328
{
    class A : IEnumerable
    {
        int[] data = { 0, 1, 2, 3, 4 };

        public IEnumerator GetEnumerator()
        {
            return new AEnumerator(this);
        }

        class AEnumerator : IEnumerator
        {
            private A parent;
            private int position = -1;

            public AEnumerator(A parent)
            {
                this.parent = parent;
            }

            public object Current
            {
                get { return parent.data[position]; }
            }

            public bool MoveNext()
            {
                position++;
                return (position < parent.data.Length);
            }

            public void Reset()
            {
                position = -1;
            }
        }
    }
    public static void Test()
    {
        A a = new A();

        foreach (var item in a)
        {
            Console.WriteLine(item);
        }

        Console.WriteLine("--- First foreach finished. ---");

        foreach (var item in a)
        {
            Console.WriteLine(item);
        }
    }
}
于 2012-07-13T17:23:58.467 に答える
0

リードもカルロスも正しい。これを行う方法の 1 つを次に示しint[]ますIEnumerableIEnumerable<int>

class A : IEnumerable
{ 
    int[] data = { 0, 1, 2, 3, 4 }; 

    public IEnumerator GetEnumerator() 
    { 
        return data.GetEnumerator(); 
    } 
} 

または、より厳密に型指定するには、次の一般的な形式を使用できますIEnumerable

class A : IEnumerable<int>
{
    int[] data = { 0, 1, 2, 3, 4 };

    public IEnumerator<int> GetEnumerator()
    {
        return ((IEnumerable<int>)data).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return data.GetEnumerator();
    }
}
于 2012-07-13T17:29:32.607 に答える