众所周知,在 C# 中,如果要让集合支持 foreach 语句,必须实现 IEnumerable 接口,该接口只有一个方法——GetEnumerator(),这个方法返回一个 IEnumerator 的对象,实际上 foreach 是使用 IEnumerator 对象进行迭代的,使用 foreach 语句与下述语句大致相当:
IEnumerator enumerator = myCollection.GetEnumerator();
while(enumerator.MoveNext)
{
MyCollectionItem item = enumerator.Current;
……
}
既然这样,为什么不直接让集合实现 IEnumerator 接口,而要通过 IEnumerable 接口来进行中转呢?于是有下面的实现:
class MyCollection: IEnumerable, IEnumerator
{
…
public IEnumerator GetEnumerator()
{
return this;
}
public object Current
{
get { …… }
}
public bool MoveNext()
{ …… }
public void Reset()
{ …… }
}
这样简单多了,并且 foreach 仍然能够正常运作。那为什么还需要 IEnumerable 接口呢?
在迭代器模式中,有一句话:将遍历机制与集合分离开来。这话像白开水一样,不引人注目,我们很容易就把它忽略了,于是才会有上面的疑问。试想一下,如果将 IEnumerator 与集合本身放在一起,在多线程环境中,多个线程都需要遍历同一个集合,该怎么办?一个对象只有一个 Current。当然,可以利用线程的同步机制来解决,但是这个方法很笨,并且,如果不是多线程,而是嵌套的两个 foreach,那就没辄了。其实玄机就在于“将遍历机制与集合分离开来”,分离后,如果要同时遍历同一个集合,只要创建一个新的包含遍历机制的类实例即可,而这就是 IEnumerable 的意图。
不过,还有一个问题没有解决,就是如何提供多种遍历机制,比如说正序遍历和逆序遍历。这需要再转一个弯:为集合添加新的方法,返回 IEnumerable,如:
class MyCollection: IEnumerable
{
... ...
public IEnumerator GetEnumerator()
{
return new NormalEnumerator(this);
}
public IEnumerable Reverse() //注意:返回的是 IEnumerable, 不是 IEnmuerator
{
return new ReverseEnumerable(this);
}
}
class ReverseEnumerable: IEnumerable //再次实现 IEnumerator 接口,以实现逆序遍历
{... ... }
然后,可以在 foreach 中如下调用:
foreach(MyCollectionItem item in myCollection.Reverse())
OK,一个标准的迭代器模式完成。