漫谈迭代模式与IEnumerable
迭代模式与IEnumerable
一个类型要实现foreach遍历就必须先实现IEnumerable接口和IEnumerator接口,IEnumerable接口和IEnumerator接口的工作原理也正是设计模式中迭代模式最好的例子。在理解IEnumerable接口和IEnumerator接口之前,先来理解迭代模式是如何实现的,对理解IEnumerable接口和IEnumerator接口的工作原理很有帮助。
Aggregate:聚合
Iterator:迭代器
Client:客户
如图所示,聚合(Aggregate)与迭代器(Iterator)分别为抽象的接口,通过具体的实现,具体的聚合(ConcreteAggregate)与具体的迭代器(ConcreteIterator)分别建立了依赖关系和聚合关系,实现迭代器(Iterator)通过客户(Client)对聚合(Aggregate)的迭代操作。
OO版迭代模式
客户类(Client)
客户类(Client):是建立迭代模式的具体载体,最终是实现迭代器对聚合的客户类进行顺序迭代输出
public class Client { public string Name { get; set; } public override string ToString() { return Name; } }
聚合(Aggregate):抽象聚合接口(Aggregate)创建一个用于查询自己的迭代器,它不关心自己存储的聚合元素,也不关心指定的迭代器是谁,因为那是具体的聚合类,而具体的聚合类(ConcreteAggregate)定义一个具体的客户类(Client)集合,同时实现了CreatedIterator()创建自己指定的迭代器(依赖关系0,便于检索也定义了索引器,Index属性,Count属性,Add方法用于添加集合元素操作。
聚合接口(Aggregate)
public interface Aggregate { Iterator CreatedIterator(); }
具体的聚合类(ConcreteAggregate)实现聚合接口(Aggregate)
public class ConcreteAggregate : Aggregate { //线性结构存储 protected List<Client> ClientList = new List<Client>(); //通过索引器以只读的方式将数据结构对外公开 public Client this[int index] { get { return ClientList[index]; } } public int Index { get; set; } public int Count { get { return ClientList.Count; } } public void Add(Client client) { ClientList.Add(client); } //指定所用的索引器,与索引器之间建立依赖关系 public Iterator CreatedIterator() { return new ConcreteIterator(this); } }
迭代器(Iterator)
迭代器的任务是查找出所要查找的集合元素,下一个元素Next(),当前元素Current(),重置元素Reset(),它也不关心将要查找哪个集合,具体实现的迭代器类(ConcreteIterator)则要负责实现Next(),Current(),Reset()成员,同时建立了与聚合类的关联关系,注意,在构造函数中对关联的集合类进行初始化操作,也正是这样聚合类才和迭代器形成依赖关系,注意依赖关系和关联关系是如何用代码的方式实现的
迭代器接口(Iterator)
public interface Iterator { bool Next(); Client Current(); void Reset(); }
具体的迭代器类(ConcreteIterator)实现迭代器接口(Iterator)
public class ConcreteIterator : Iterator { //与聚合类形成关联关系 ConcreteAggregate Agg = null; public ConcreteIterator(ConcreteAggregate sch) { //指定所要查询的聚合类 Agg = sch; Agg.Index = -1; } /// <summary> /// Next()为指向下一个对象并 /// 判断是否到达集合的末尾 /// </summary> public bool Next() { // 当聚合类的索引不等于集合长度时,继续向下查找,返回true并加一操作,否则返回false表示查询已结束 return (++Agg.Index != Agg.Count) ? true : false; } /// <summary> /// Current()利用索引器取出当前集合元素 /// </summary> public Client Current() { return Agg[Agg.Index]; } /// <summary> /// 重置下标 /// </summary> public void Reset() { Agg.Index = -1; } }
主函数调用
static void Main(string[] args) { //建立聚合类并添加聚合元素 ConcreteAggregate aggregate = new ConcreteAggregate(); aggregate.Add(new Client() { Name = "ABC" }); aggregate.Add(new Client() { Name = "CBA" }); aggregate.Add(new Client() { Name = "EEELab" }); //取出聚合类指定的迭代器,并对迭代器进行操作 Iterator iter = aggregate.CreatedIterator(); while (iter.Next()) { Console.WriteLine(iter.Current()); } }
IEnumerable接口和IEnumerator接口
在理解了前面的迭代模式后,在理解下面的IEnumerable接口和IEnumerator接口实现工作原理也就不成问题。你会发现这两个接口的实现正是一个典型的迭代模式。
客户类(Client)
public class Client { public string Name { get; set; } public override string ToString() { return Name; } }
聚合类(ConcreteAggregate)实现IEnumerable接口
public class ConcreteAggregate : System.Collections.IEnumerable { protected List<Client> ClientList = new List<Client>(); public int Index { get; set; } public int Count { get { return ClientList.Count; } } public Client this[int index] { get { return ClientList[index]; } } public void Add(Client client) { ClientList.Add(client); } //对应于OO版本的CreatedIterator()方法 public System.Collections.IEnumerator GetEnumerator() { return new ConcreteIterator(this); } }
具体的迭代器类(ConcreteIterator)实现IEnumerator接口
public class ConcreteIterator : System.Collections.IEnumerator { ConcreteAggregate school = null; public ConcreteIterator(ConcreteAggregate sch) { school = sch; school.Index = -1; } //对应OO版本的Current() object System.Collections.IEnumerator.Current { get { return school[school.Index]; } } //对应OO版本的Next() public bool MoveNext() { school.Index++; return (school.Index != school.Count) ? true : false; } //对应OO版本的Reset() public void Reset() { school.Index = -1; } }
主函数调用
static void Main(string[] args) { //建立聚合类并添加聚合元素 ConcreteAggregate aggregate = new ConcreteAggregate(); aggregate.Add(new Client() { Name = "ABC" }); aggregate.Add(new Client() { Name = "CBA" }); aggregate.Add(new Client() { Name = "EEELab" }); //取出聚合类指定的迭代器,并对迭代器进行操作 IEnumerator Ienumerator = aggregate.GetEnumerator(); while (Ienumerator.MoveNext()) { Console.WriteLine(Ienumerator.Current); } }
你会发现,IEnumerable接口和IEnumerator接口分别对应着聚合接口(Aggregate)和迭代器接口(Iterator),因此,实现IEnumerable接口和IEnumerator接口是实现迭代模式的典型例子,对理解如何让自己定义的类能够实现foreach遍历来说很有意义。
在主函数中用foreach调用聚合类(ConcreteAggregate)
static void Main(string[] args) { ConcreteAggregate aggregate = new ConcreteAggregate(); aggregate.Add(new Client() { Name = "ABC" }); aggregate.Add(new Client() { Name = "CBA" }); aggregate.Add(new Client() { Name = "EEELab" }); System.Collections.IEnumerator Ienumerator = aggregate.GetEnumerator(); while (Ienumerator.MoveNext()) { Console.WriteLine(Ienumerator.Current); } foreach (Client item in aggregate) { Console.WriteLine(item.Name); } }
之所以foreach要用继承于IEnumerable接口的索引聚合类(ConcreteAggregate)是因为foreach直接调用索引集合的所继承IEnumerator接口的Current属性和MoveNext()成员方法。
下面是foreach的CIL代码,从代码可以看出foreach是在内部隐式的执行循环指令来实现我们在迭代模式中的循环操作,因此,从根本上说,执行foreach操作就是一个迭代模式的执行过程。
foreach的CIL代码
.try
{
IL_0087: br.s IL_00a4 //跳转到执行MoveNext()的上一行
IL_0089: ldloc.s CS$5$0001
IL_008b: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current() //Current属性
IL_0090: castclass IteratorNET.Client
IL_0095: stloc.2
IL_0096: nop
IL_0097: ldloc.2
IL_0098: callvirt instance string IteratorNET.Client::get_Name()//Name属性
IL_009d: call void [mscorlib]System.Console::WriteLine(string)
IL_00a2: nop
IL_00a3: nop
IL_00a4: ldloc.s CS$5$0001
IL_00a6: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
IL_00ab: stloc.s CS$4$0000
IL_00ad: ldloc.s CS$4$0000
IL_00af: brtrue.s IL_0089 //跳转回取Current属性的上一行
IL_00b1: leave.s IL_00d0
} // end .try
finally
{
IL_00b3: ldloc.s CS$5$0001
IL_00b5: isinst [mscorlib]System.IDisposable
IL_00ba: stloc.s CS$0$0002
IL_00bc: ldloc.s CS$0$0002
IL_00be: ldnull
IL_00bf: ceq
IL_00c1: stloc.s CS$4$0000
IL_00c3: ldloc.s CS$4$0000
IL_00c5: brtrue.s IL_00cf
IL_00c7: ldloc.s CS$0$0002
IL_00c9: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_00ce: nop
IL_00cf: endfinally
} // end handler