8

我基本上是想让我的班级能够使用foreach. 我读了这个教程。微软_ 这似乎很简单。但是,当我想第二次迭代时遇到问题。我调试了它;事实证明它没有调用该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()函数。所以,在一个循环之后,我再也无法迭代我的课程了。

主要的

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调用时,它都会要求一个 IEnumerator的. 返回您的类实例是一个坏主意 - 您应该创建一个单独的类来实现IEnumerator,并返回它。

这通常通过使用嵌套(私有)类并返回它的实例来完成。您可以将 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 上将其标记为 [已过时]!

此外,许多序列是不可重复的。考虑一下位于 NetworkStream 或随机数生成器上的迭代器。因为迭代器(在一般情况下)不需要是可重复的,所以您应该尽可能地针对它们最多迭代一次。如果不可能的话,也许可以通过 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

里德和卡洛斯都是正确的。这是您可以做到这一点的一种方法,因为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 回答