2

根据Albahari 兄弟 [Page No.273]的说法,之所以使用,IEnumerable是因为:

通过定义返回枚举器的单个方法,IEnumerable提供了灵活性,即

->迭代逻辑可以framed off to another class理解

-> Moreover it means that several consumers can enumerate the collection at once without interfering with each other不明白

我无法理解第二点!

如何IEnumerable不使用IEnumerator启用多个消费者一次枚举集合

4

4 回答 4

4

IEnumerable实现一个方法,GetEnumerator(),它返回一个IEnumerator. 因为每次调用该方法时,IEnumerator都会返回一个新的,它有自己的状态。这样,多个线程可以迭代同一个集合,而不会有一个线程更改另一个线程的当前指针的危险。

如果一个集合实现了IEnumerator,那么它一次只能被一个线程有效地迭代。考虑以下代码:

public class EnumeratorList : IEnumerator
{
    private object[] _list = new object[10];
    private int _currentIndex = -1;

    public object Current { get { return _list[_currentIndex] } };

    public bool MoveNext()
    {
        return ++_currentIndex < 10;
    }

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

鉴于该实现,如果两个线程尝试同时遍历 EnumeratorList,它们将得到交错的结果,并且不会看到整个列表。

如果我们将其重构为IEnumerable,则多个线程可以访问同一个列表而不会出现此类问题。

public class EnumerableList : IEnumerable
{
    private object[] _list = new object[10];

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

    private object this[int i]
    {
        return _list[i];
    }

    private class ListEnumerator : IEnumerator
    {
        private EnumeratorList _list;
        private int _currentIndex = -1;

        public ListEnumerator(EnumeratorList list)
        {
            _list = list;
        }

        public object Current { get { return _list[_currentIndex] } };

        public bool MoveNext()
        {
            return ++_currentIndex < 10;
        }

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

现在这是一个简单的、人为的例子,当然,但我希望这有助于使它更清楚。

于 2012-07-25T18:09:58.710 回答
1

实现的类型IEnumerator必须具有方法和属性,以便您可以对其进行迭代,即MoveNext()Reset()Current。如果你有多个线程同时尝试迭代这个对象会发生什么?MoveNext()当它们都调用相同的函数时,它们会互相踩踏,这会修改相同的Current属性。

实现的类型IEnumerable必须能够提供IEnumerator. 现在当多个线程迭代对象时会发生什么?每个线程都有一个单独的 IEnumerator 对象实例。返回的 IEnumerator 与您的集合的对象类型不同。它们是完全不同的东西。但是,它们确实知道如何获取下一个项目并显示集合的当前项目,并且每个对象都将拥有自己的有关当前枚举状态的内部数据。因此,它们不会互相踩踏并安全地从单独的线程迭代您的集合。

有时,集合类型会为自己实现 IEnumerator(它是它自己的枚举器),然后通过返回自身来实现 IEnumerable。在这种情况下,对于多个线程,您将一无所获,因为它们仍然使用同一个对象进行枚举。这是倒退。相反,正确的过程是首先为您的集合实现一个单独的(可以嵌套的)枚举器类型。然后,您通过返回该类型的新实例来实现 IEnumerable,并通过保留私有实例来实现 IEnumerator。

于 2012-07-25T18:18:19.500 回答
1

考虑代码:

    class Program
{
    static void Main(string[] args)
    {
        var test = new EnumTest();
        test.ConsumeEnumerable2Times();
        Console.ReadKey();
    }
}

public class EnumTest
{
    public IEnumerable<int>  CountTo10()
    {
        for (var i = 0; i <= 10; i++)
            yield return i;
    }

    public void ConsumeEnumerable2Times()
    {
        var enumerable = CountTo10();

        foreach (var n in enumerable)
        {
            foreach (int i in enumerable)
            {
                Console.WriteLine("Outer: {0}, Inner: {1}", n, i);
            }
        }
    }
}

此代码将产生输出:

Outer: 0, Inner: 1
Outer: 0, Inner: 2
...
Outer: 1, Inner: 0
Outer: 1, Inner: 1
...
Outer: 10, Inner: 10

使用 IEnumerable 您可以一遍又一遍地枚举同一个集合。IEnumerable 实际上将为每个枚举请求返回一个新的 IEnumerator 实例。

在上面的示例中,方法 EnumTest() 被调用一次,但返回的 IEnumerable 被使用了 2 次。每次独立数到 10。

这就是为什么“几个消费者可以一次枚举集合而不会相互干扰”。您可以将相同的 IEnumerable 对象传递给 2 个方法,它们将独立枚举集合。使用 IEnumerator 你无法做到这一点。

对不起我的英语。

于 2012-07-25T18:07:05.980 回答
1

MSDN 文章为正确使用提供了一个很好的IEnumerable示例。

为了直接回答您的问题,正确实施后,IEnumerator用于遍历集合的对象对于每个调用者都是唯一的。这意味着您可以让多个消费者调用foreach您的集合,每个消费者都有自己的枚举器,并在集合中拥有自己的索引。

请注意,这仅提供对集合进行修改的基本保护。为此,您必须使用适当的lock()块(参见此处)。

于 2012-07-25T18:05:12.747 回答