根据Albahari brothers[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
启用多个消费者一次枚举该集合?
答案 0 :(得分: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;
}
}
}
现在这是一个简单,人为的例子,但我希望这有助于使其更加清晰。
答案 1 :(得分:1)
IEnumerable
的{{3}}提供了正确使用的一个很好的例子。
要直接回答您的问题,正确实施后,用于浏览集合的IEnumerator
对象对每个调用者都是唯一的。这意味着您可以让多个消费者在您的集合上调用foreach
,每个消费者都有自己的枚举器,并在集合中有自己的索引。
请注意,这仅提供对集合修改的基本保护。为此,您必须使用正确的lock()
块(请参阅MSDN article)。
答案 2 :(得分: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,你无法实现这一目标。
对不起我的英语。
答案 3 :(得分:1)
实现IEnumerator
的类型必须具有方法和属性,以便您可以对其进行迭代,即MoveNext()
,Reset()
和Current
。如果您有多个线程试图同时迭代此对象会发生什么?他们会相互踩,因为他们都调用相同的MoveNext()
函数,这会修改相同的Current
属性。
实现IEnumerable
的类型必须能够提供IEnumerator
的实例。现在当多个线程迭代对象时会发生什么?每个线程都有一个IEnumerator对象的单独实例。返回的IEnumerator与您的集合不是同一类型的对象。它们完全不同。但是,他们确实知道如何获取下一个项目并显示集合的当前项目,并且每个对象都将拥有关于枚举当前状态的内部数据。因此,它们不会相互踩踏并安全地从单独的线程中迭代您的集合。
有时,集合类型会为自己实现IEnumerator(它是它自己的枚举器),然后通过返回自身来实现IEnumerable。在这种情况下,您将无法获得多个线程,因为它们仍然使用相同的对象进行枚举。这是倒退。相反,正确的过程是首先为集合实现单独的(可嵌套的)枚举器类型。然后通过返回该类型的新实例来实现IEnumerable,并通过保留私有实例来实现IEnumerator。