提到迭代器我们不能不想到迭代器模式,那我就以迭代器模式作为开场白.
在我们的应用程序中常常有这样一些数据结构:
它们是一个数据的集合,如果你知道它们内部的实现结构就可以去访问它们,它们各自的内部存储结构互不相同,各种集合有各自的应用场合.说到这里大家可能想出一大堆这样的集合了:List,Hashtable,ArrayList等等。这些集合各自都有各自的个性,这就是它们存在的理由。但如果你想遍历它你必须知道它内部的存储细节,作为一个集合元素,把内部细节暴露出来肯定就不好了,这样客户程序就不够稳定了,在你更换集合对象的时候,比如List不能满足需求的时候,你换成Hashtable,因为以前的客户程序过多的关注了List内部实现的细节,所以不能很好的移植。而迭代器模式就是为解决这个问题而生的:
提供一种一致的方式访问集合对象中的元素,而无需暴露集合对象的内部表示。
比如现在有这样一个需求,遍历集合内的元素,然后输出,但是并不限定集合是什么类型的集合,也就是未来集合可能发生改变。
思考:
集合会发生改变,这是变化点,集合改变了,遍历方法也改变,我们要保证遍历的方法稳定,那么就要屏蔽掉细节。找到了变化点那我们就将其隔离起来(一般使用interface作为隔离手段):假设所有的集合都继承自ICollection接口,这个接口用来隔离具体集合的,将集合屏蔽在接口后面,作为遍历我们肯定需要这样一些方法:MoveNext,Current,既然ICollection负责数据存储,职责又要单一,那么就新建立一个接口叫做Iterator吧,每种具体的集合都有自己相对应的Iterator实现:
下面是一个简易的实现代码:
/// <summary> /// 集合的接口 /// </summary> public interface ICollection { int Count { get; } /// <summary> /// 获取迭代器 /// </summary> /// <returns>迭代器</returns> Iterator GetIterator(); } /// <summary> /// 迭代器接口 /// </summary> public interface Iterator { bool MoveNext(); object Current { get; } } public class List : ICollection { private const int MAX = 10; private object[] items; public List() { items = new object[MAX]; } public object this[int i] { get { return items[i]; } set { this.items[i] = value; } } #region ICollection Members public int Count { get { return items.Length; } } public Iterator GetIterator() { return new ListIterator(this); } #endregion } public class ListIterator : Iterator { private int index = 0; private ICollection list; public ListIterator(ICollection list) { this.list = list; index = 0; } #region Iterator Members public bool MoveNext() { if (index + 1 > list.Count) return false; else { index++; return true; } } public object Current { get { return list[index]; } } #endregion } /// <summary> /// 测试 /// </summary> public class Program { static void Main() { ICollection list = new List(); Iterator iterator = list.GetIterator(); while (iterator.MoveNext()) { object current = iterator.Current; } } }</div>
看看最后的测试,是不是不管具体的集合如何改变,遍历代码都非常稳定?而且扩展新的集合类也非常方便,只是添加代码不会修改原来的代码,符合开闭原则。当然,这么好的解决方案微软当然不会放过,现在C# 2.0里已经内置了对迭代器的支持,看看System.Collections, System.Collections.Generic命名空间,所有的集合都实现了这个接口:IEnumerable,这个接口还有泛型的版本。注意到这个接口只有一个方法:IEnumerator GetEnumerator();,IEnumerator就是迭代器的接口,相当于我的实例里面的Iterator,它也有泛型的版本。
那么现在在.net里所有的集合类都可以这样访问了:
IEnumerator ienumerator = list.GetEnumerator();
while(ienumerator.MoveNext())
{
object current = ienumerator.Current;
}
</div>
但是这样访问也太麻烦了,所以C#里出现了foreach关键字,我们来看看foreach背后发生了什么?假如有如下的代码:
public static void Main() { ArrayList list = new ArrayList(); list.Add(1); list.Add(2); list.Add(3); foreach (object item in list) { Console.WriteLine(item.ToString()); } }</div>
下面是它对应的IL代码:
.method private hidebysig static void Main() cil managed { .entrypoint .maxstack 2 .locals init ( [0] class [mscorlib]System.Collections.ArrayList list, [1] object item, [2] class [mscorlib]System.Collections.IEnumerator CS$5$0000, [3] class [mscorlib]System.IDisposable CS$0$0001) L_0000: newobj instance void [mscorlib]System.Collections.ArrayList::.ctor() L_0005: stloc.0 L_0006: ldloc.0 L_0007: ldc.i4.1 L_0008: box int32 L_000d: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object) L_0012: pop L_0013: ldloc.0 L_0014: ldc.i4.2 L_0015: box int32 L_001a: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object) L_001f: pop L_0020: ldloc.0 L_0021: ldc.i4.3 L_0022: box int32 L_0027: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object) L_002c: pop L_002d: ldloc.0 L_002e: callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Collections.ArrayList::GetEnumerator() L_0033: stloc.2 L_0034: br.s L_0048 L_0036: ldloc.2 L_0037: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current() L_003c: stloc.1 L_003d: ldloc.1 L_003e: callvirt instance string [mscorlib]System.Object::ToString() L_0043: call void [mscorlib]System.Console::WriteLine(string) L_0048: ldloc.2 L_0049: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext() L_004e: brtrue.s L_0036 L_0050: leave.s L_0063 L_0052: ldloc.2 L_0053: isinst [mscorlib]System.IDisposable L_0058: stloc.3 L_0059: ldloc.3 L_005a: brfalse.s L_0062 L_005c: ldloc.3 L_005d: callvirt instance void [mscorlib]System.IDisposable::Dispose() L_0062: endfinally L_0063: call string [mscorlib]System.Console::ReadLine() L_0068: pop L_0069: ret .try L_0034 to L_0052 finally handler L_0052 to L_0063 }</div>
从.locals init 那里可以看出编译器为我们添加了两个局部变量,一个就是迭代器。
L_002d: ldloc.0
L_002e: callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Collections.ArrayList::GetEnumerator()
L_0033: stloc.2
</div>
这三行代码告诉我们,调用list的GetEnumerator()方法,获取迭代器实例将其赋值给编译器为我们添加的那个迭代器局部变量,接着是L_0034: br.s L_0048,
br.s这个指令是强制跳转,我们接着看
L_0048: ldloc.2
L_0049: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
</div>
调用迭代器的MoveNext()方法,L_004e: brtrue.s L_0036 如果是true的话跳转,