背景
由于枚举器存在遍历二叉树不方便的问题。才有了迭代器。在了解了迭代器之前必须了解枚举器。
迭代器就是带了状态机的枚举器。
迭代器简介
至此,你已了解 foreach 的内部实现代码,是时候了解如何使用迭代器创建 IEnumerator<T>、IEnumerable<T> 和自定义集合对应的非泛型接口的自定义实现代码了。迭代器提供明确的语法,用于指定如何迭代集合类中的数据,尤其是使用 foreach 循环。这样一来,集合的最终用户就可以浏览其内部结构,而无需知道相应结构。
枚举模式存在的问题是,手动实现起来不方便,因为必须始终指示描述集合中的当前位置所需的全部状态。对于列表集合类型类,指示这种内部状态可能比较简单;当前位置的索引就足够了。相比之下,对于需要递归遍历的数据结构(如二叉树),指示状态可能就会变得相当复杂。为了减少实现此模式所带来的挑战,C# 2.0 新增了 yield 上下文关键字,这样类就可以更轻松地决定 foreach 循环如何迭代其内容。
语法信息
1、迭代器可用作一种方法,或一个 get 访问器。 不能在事件、实例构造函数、静态构造函数或静态终结器中使用迭代器。
2、必须存在从 yield return 语句中的表达式类型到迭代器返回的 IEnumerable<T> 类型参数的隐式转换。
3、在 C# 中,迭代器方法不能有任何 in、ref 或 out 参数。
4、在 C# 中,yield 不是保留字,只有在 return 或 break 关键字之前使用时才有特殊含义。
迭代器块
迭代器块是有一个或多个yield语句的代码块。下面3种类型的代码块中的任意一种都可以是迭代器块:
- 方法主体
- 访问器主体
- 运算符主体
迭代器块与其他代码块不同。其他块包含的语句被当做命令式。即先执行代码块中的第一个语句,然后执行后面的语句,最后控制离开块。
另一方面,迭代器块不是需要在同一时间执行的一串命令式命令,而是描述了希望编译器为我们创建的枚举器类的行为。迭代器块中的代码描述了如何枚举元素。
迭代器块由两个特殊语句:
- yield return语句指定了序列中返回的下一项
- yield break语句指定在序列中没有的其他项
编译器得到有关枚举项的描述后,使用它来构建包含所有需要的方法和属性实现的枚举器类。结果类被嵌套包含在迭代器声明的类中。
如下图所示,根据迭代器块的返回类型,你可以让迭代器产生枚举器或可枚举类型。
使用迭代器来创建枚举器
class MyClass { //该类目前已实现GetEnumerator()使类本身可枚举 public IEnumerator<string> GetEnumerator()//迭代器 { yield return "black"; yield return "gray"; yield return "white"; } } class Program { static void Main() { var mc = new MyClass(); foreach (string shade in mc)////该类目前已实现GetEnumerator()使类本身可枚举 所以 写mc { Console.WriteLine(shade); } } }
反编译IL代码
下图演示了MyClass的代码及产生的对象。注意编译器为我们自动做了多少工作。
- 图左的迭代器代码演示了它的返回类型是
IEnumerator<string>
- 图右演示了它有一个嵌套类实现了
IEnumerator<string>
我们发现show类,内部自动生成一个枚举器,该枚举带了状态机的功能。并且自动继承了IEnumerator<object>, IEnumerator, IDisposable三个接口。
IEnumerator<T> 和 IEnumerator 接口的类图
至此我们一目了然,迭代器就是带了状态机的枚举器。
使用迭代器来创建可枚举类型
之前示例创建的类包含两部分:产生返回枚举器方法的迭代器以及返回枚举器的GetEnumerator方法。
本节例子中,我们用迭代器来创建可枚举类型,而不是枚举器。与之前的示例相比,本例有以下不同:
- 若实现GetEnumerator,让它调用迭代器方法以获取自动生成的实现IEnumerable的类实例。然后从IEnumerable对象返回由GetEnumerator创建的枚举器,如图右
- 若通过不实现GetEnumerator使类本身不可枚举,仍然可以使用由迭代器返回的可枚举类,只需要直接调用迭代器方法
本例中,BlackAndWhite迭代器方法返回IEnumerable<string>
而不是IEnumerator<string>
。因此MyClass首先调用BlackAndWhite方法获取它的可枚举类型对象,然后调用对象的GetEnumerator方法来获取结果,从而实现GetEnumerator方法
class MyClass { //该类目前未实现 GetEnumerator() /* public IEnumerator<string> GetEnumerator() { IEnumerable<string> myEnumerable = BlackAndWhite(); return myEnumerable.GetEnumerator(); }*
public IEnumerable<string> BlackAndWhite()//迭代器 {
yield return "black"; yield return "gray"; yield return "white"; } } class Program { static void Main() { var mc = new MyClass(); //该类目前未实现GetEnumerator()使类本身不可枚举,仍然可以使用由迭代器返回的可枚举类,只需要直接调用迭代器方法 foreach (string shade in mc.BlackAndWhite()) { Console.Write(shade); } } }
反编译IL代码