遍历器(Iterator)的作用是按照指定的顺序来访问一个集合中的所有元素,而不需要了解集合的详细数据结构。
1 概述
1.1 foreach语句
这种遍历方式对任何类型的数据都适用,因为所有数组都继承了.NET类库中定义的类System.Array,而该类继承了接口IEnmerable。在C#中,如果某个类型继承了接口IEnumerable,或者继承了泛型接口IEnumerable<T>,或者继承了泛型接口IEnumerable<T>的任何一个构造类型,那么称该类型是“可枚举的”。
可枚举类型通常表示一个集合。除数组之外,.NET类库中定义的枚举类型的例子有:字符串类String,表示XML文件节点的类XmlNode,表示动态列表的泛型类List<T>等。这些类型都可以使用foreach循环来遍历集合中的每一个元素。
1.2 Iterator模式
从很多事物中可以提取出许多共性的要素,而人们在设计过程中总是自觉不自觉地在使用这些要素,这些要素的抽象就是设计模式。
在各种各样的类库的设计中,面向对象的设计模式得到了广泛的应用。.NET类库的设计模式中使用到了Iterator模式,它是Erich Gamma总结的面向对象的设计模式这一,遍历器的概念正是对这一模式的实现。
Iterator模式的作用就是对集合中一系列元素进行访问,基本思想是:集合对象只负责维护集合中的各个元素,而对元素的访问则通过定义一个新的枚举器对象来进行;枚举器对象负责获取集合中的元素;并允许按照特定的顺序来遍历这些元素。与枚举器相关联的集合叫做可枚举集合。该模式的优点在于不需要暴露集合的内部结构,同时可以为一个集合实现多种遍历方式。
在.NET类库中,抽象可枚举集合所对应的类型可以是IEnumerable和IEnumerable<T>;如果一个非抽象类型继承了其中的任何一个接口,该类型就是一个具体的可枚举集合。抽象枚举器所对应的类型可以是IEnumerator和IEnumerator<T>,它们提供的方法成员用于查询可枚举集合的状态以及访问集合中的元素;而它们的非抽象派生类型同样都可以作为具体的枚举器。为集合获取枚举器对象的方法就是IEnumerable和IEnumerable<T>的GetEnumerator方法。
2 使用可枚举类型
2.1 IEnumerable和IEnumerable<T>接口
如果希望自己定义的集合对象也能够使用foreach循环,方法很简单,就是该类型继承接口IEnumerable或IEnumerable<T>。这两个接口都只定义了一个成员方法,其原型分别为:
IEnumerator GetEnumerator(); IEnumerator<T> GetEnumerator();
从这两个接口中派生的可枚举类型就都需要实现这两个接口方法。
public class Assemble<T> : IEnumerable { //字段 protected T[] m_list; ... //遍历器方法 public IEnumerator GetEnumerator() { for(int i=0;i<m_count;i++) yield return m_list[i]; } } }
通过GetEnumerator方法就得到了可枚举类型Assemble<T>的一个枚举器,通过它能够遍历集合m_list中的每个对象。该方法就是一个遍历器,而方法的执行代码就叫做遍历器代码。代码中使用的yield return语句用于依次生成一系列对象,其中的每一个都可以在foreach循环语句中被遍历,且遍历顺序与yield return语句生成的顺序一致。
yield return语句所生成对象的类型,必须与可枚举类型所管理的集合元素类型一致,或是可以隐式转换为这些元素的类型。如果可枚举类型继承的接口是IEnumerable,其元素类型就是object,这样yield return语句后面就可以是任何类型的表达式,但在foreach循环遍历时通常就要进行装箱和拆箱转换。而如果可枚举类型继承的是接口IEnumerable<T>或其某个构造类型,相应的元素类型就为T或其封闭类型。对于Assemble<T>这样的泛型类,更常见的情况是使其继承泛型接口IEnumerable<T>,这样就避免了类型转换的问题。
public class Assemble<T> : IEnumerable<T> { //字段 protected T[] m_list; ... //遍历器方法 public IEnumerator<T> GetEnumerator() { for(int i=0;i<m_count;i++) yield return m_list[i]; } } }
2.2 实现多种遍历方式
通过GetEnumerator方法所实现的遍历器叫做默认遍历器。C#还支持为同一个枚举类型实现多个遍历器,从而以不同的方式来遍历集合中的元素。非默认的遍历器也是通过方法成员来实现的,不同的是,它们要求方法成员的返回类型为IEnumerable或IEnumerable<T>。
public class Assemble<T> : IEnumerable<T> { //字段 protected T[] m_list; ... //遍历器方法 public IEnumerator<T> GetEnumerator() { for(int i=0;i<m_count;i++) yield return m_list[i]; } } public IEnumerable<T> GetReverseEnumerator() { for(int i=m_count-1;i>=0;i--) yield return m_list[i]; } }
这样就可以实现正向和逆向地输出集合的所有元素。
foreach语句中,关键字in后面的对象类型必须为可枚举类型。在可枚举类型定义代码中出现的this关键字所代表的对象显然也是可枚举的,因此可以将它作为非默认遍历器的返回值。例如,可以为泛型类Assemble<T>再增加下面的属性定义,而它所实现的遍历方式与默认遍历器相同:
public IEnumerable<T> ObverseEnumerator { get { return this; } }
下面是一个完整的示例:
程序代码还在调试,稍后修正
2.3 带参遍历
实现非默认遍历器方法还可以带有参数,通过传递参数来控制遍历的过程。
3 使用枚举器
4 遍历器工作机制
4.1 遍历器代码
4.2 遍历流程
5 示例程序:联系人分类输出