yield 语句的深入理解
要理解 yield 语句,首先要了解 .Net 的迭代器(iterator)。
在计算机内存里,数据往往是按一定数据结构存储的。
.Net框架提供了许多常见的结构,比如 List
这些结构称为集合(collection),是容器(container)的一部分。collection 常按一定顺序来访问其元素,此时也称为序列(sequence)。
iterator 是一个对象,用来遍历 collection 中的元素。
具体的,iterator 就是 IEnumerable
IEnumerable 和 IEnumerator 接口是与之对应的弱类型版本,它们的元素类型为 object。
IAsyncEnumerable
IEnumerable
public interface IEnumerable
方法 public
System.Collections.Generic.IEnumerator
返回 enumerator
IEnumerator
public interface IEnumerator
属性 public T Current { get; }
返回 collection 中 enumerator 当前位置的元素
IEnumerable 接口定义
public interface IEnumerable
方法 public
System.Collections.IEnumerator GetEnumerator ()
返回 enumerator 用来遍历 collection
IEnumerator 接口定义
public interface IEnumerator
属性 public object Current { get; }
返回 collection 中 enumerator 当前位置的元素
方法 public bool MoveNext ()
把 enumerator 推进到 collection 的下一个元素,成功推进返回true,越过 collection 的尾端返回false
方法 public void Reset ()
把 enumerator 的初始位置,即第一个元素之前的位置。
foreach 语句的执行
请看下面这个 foreach 语句的例子,其中 collection 的类型为 IEnumerable
编译器产生的代码大概是这样子
下面是自定义实现一个 IEnumerable
首先是 PrimeNumberGenerator 类,它实现 IEnumerable
其次是 PrimeNumberEnumerator 类,它实现 IEnumerator
使用 PrimeNumberGenerator 类的演示代码片段
改为使用 yield 语句来实现,取代前面的 PrimeNumberGenerator 类和 PrimeNumberEnumerator 类。
是不是很简单明了?运行结果完全一样。
使用 dotPeek 工具来反编译上面使用了 yield 语句的 GetPrimeNumbers 方法,其底层实现代码如下所示
由反编译的代码可以看出,编译器自动生成了一个类, 帮你实现了IEnumerable
相比手工实现要编写一大堆固定模式、复杂易错的代码,yield 语句确实带来了很多方便。
而且,要特别地注意到,在上面的例子中,当调用 GetPrimeNumbers 时,质数序列的元素此时并没有产生,它只是创建了一个类实例。 只有在 foreach 语句执行过程中,不断调用接口的 MoveNext 方法和 Current 属性,序列元素才会被产生和访问, 一个接一个地进行。
使用 yield 语句的方法称为迭代器方法,它们的执行过程都是这样的。