22 迭代器
22.1 迭代器块
迭代器块就是产生值的有序序列的语句块。迭代器块通过一个或多个 yield 语句区别于常规语句块。
l yield return 语句产生迭代的下一个值。
l yield break 语句指明迭代完成。
迭代器块可以被用作一个方法体( method-body )、运算符体( operator-body )、访问器体( accessor-body ) , 前提是对应函数成员的返回类型是枚举器 (enumerator) 接口之一或者可枚举 (enumerable) 接口之一。
迭代器块在 C# 语法中不什么独特的元素。它们在几个方面受到限制,并且主要的作用在函数成员声明的语义上,但它们在语法上只是语句块而已。
当一个函数成员使用迭代器块实现时,对于正式参数列表指定任何 ref 或 out 参数将导致编译时错误。
return 语句出现在迭代器块中将导致编译时错误(但 yield return 语句是允许的)。
在迭代器块中包含不安全上下文 (§18.1) 将导致编译时错误。即使是当迭代器声明内嵌在不安全上下文中,迭代器块也总是定义为一个安全上下文。
22.1.1 枚举器接口
** 枚举器接口 (enumerator interface) ** 是 System.Collections.IEnumerator 接口以及 System.Collections.Generic.IEnumerator
1<t> 的所有实例。在本章,这些接口将相应地作为 IEnumerator 和 IEnumerator<t> 而引用。
2
3### 22.1.2 可枚举接口
4
5** 可枚举接口( enumerable interface ** ** ) ** 是 System.Collections.IEnumerable 接口和 System.Collections.Generic.IEnumerable<t> 的所有实例。在本章,这些接口将相应地作为 IEnumerable 和 IEnumerable<t> 而被引用。
6
7
8
9
10### 22.1.3Yield 类型
11
12迭代器块生成具有相同类型的所有值的序列。给类型被称为迭代器块的 ** yield ** ** 类型( yield type ** ** ) ** 。
13
14l 迭代器块的 yield 类型通常用于实现返回 IEnumerator 或 IEnumerable 是对象的函数成员。
15
16l 迭代器块的 yield 类型通常用于实现返回 IEnumerator<t> 或 IEnumerable<t> 是 T 的函数成员。
17
18### 22.1.4 this 访问
19
20在类的实例成员的迭代器块内, this 表达式被归类为值。该值的类型就是类类型,在这个类型可以采用这种用法,这个值就是成员被调用时的对象的引用。
21
22在结构的实例成员的迭代器块内, this 表达时被归类作为一个变量。该变量的类型就是结构类型,在这个结构中它可以采用这种用法。该变量表示一个成员被调用时的对应结构的一个拷贝。在结构实例成员的迭代器块内, this 变量的行为就好像是结构类型的一个值参数。
23
24## 22.2 枚举对象
25
26当返回枚举器接口类型的函数成员使用迭代器块实现时,调用函数成员并不会立即执行迭代器块中的代码。相反,枚举器对象( enumerator object )将被创建和返回。该对象封装了在迭代器块中指定的代码,当枚举器对象的 MoveNext 方法被调用时,迭代器块中的代码就会执行。枚举器对象有如下的特征。
27
28l 它实现了 IEnumerator 和 IEnumerator<t> , T 是迭代器块的 yield 类型 ( 产生类型 ) 。
29
30l 它实现了 System.IDisposable 。
31
32
33
34
35l 它被使用实参值(如果有的话)的拷贝而初始化,而实例值将被传递给函数成员。
36
37l 它有四个潜在的状态 before 、 running 、 suspended 和 after ,并且它在 before 状态之前被初始化。
38
39枚举器对象通常是一个编译器生成的枚举器类实例,它封装了迭代器语句块中的代码,并且实现了枚举器接口,但其它实现方法也是可以的。如果一个枚举器类是由编译器生成的,这个类将会是内嵌的,在包含函数成员的类中,类将具有私有可访问性,并且该类具有一个保留为编译器所用的名字 (§2.4.2) 。
40
41枚举器对象可以实现比在此指定的更多接口。
42
43随后几节描述了由 IEnumerable 和 IEnumerable<t> 接口实现的 MoveNext 、 Current 、和 Dispose 成员的确切行为,这两个接口由枚举对象提供。
44
45请注意,枚举器对象并不支持 IEnumerator.Reset 方法。调用该方法将会抛出 System.NotSupportedException 异常。
46
47## 22.2.1MoveNext 方法
48
49枚举器对象的 MoveNext 方法封装了迭代器块的代码。调用 MoveNext 方法将执行迭代器内的代码,并将枚举对象的 Current 属性设置为合适的值。由 MoveNext 方法执行的精确动作,取决于当 MoveNext 方法被调用时枚举器对象的状态。
50
51l 如果枚举器对象状态是 before ,调用 MoveNext
52
53n 将把状态改为 running 。
54
55n 将把迭代器块的参数(包括 this )初始化为,当枚举器对象被初始化而保存的实参值和实例值。
56
57n 从开始执行迭代器块直到执行被中断(如下面所描述的)。
58
59l 如果枚举器对象的状态是 running ,调用 MoveNext 的结果是未指定的。
60
61l 如果枚举器对象的状态是 suspended ,调用 MoveNext
62
63n 将把状态改为 running 。
64
65
66
67
68l 恢复所有局部变量和参数(包括 this )的值为迭代器最后一次挂起( suspended )时执行状态的值。请注意,由这些变量所引用的任何对象的内容,都可能因为前一次对 MoveNext 的调用而改变。
69
70n 在引发执行挂起的 yield return 语句之后重新开始执行迭代器块,并且这个状态会继续直到执行被中断(如下所描述)。
71
72l 如果枚举器对象的状态是 after ,那么调用 MoveNext 将返回 false 。
73
74当 MoveNext 执行迭代器块时,有四种方法可以中断执行:通过一个 yield return 语句,通过一个 yield break 语句,到达迭代器块的结束点,以及一个异常被抛出,并被传播到迭代器块之外。
75
76l 当遇到一个 yield return 语句时 (§ 22.4 ) ,将会发生如下情况
77
78n 在该语句中被给定的表达式将被计算,隐式地转换到产生类型( yield type ) , 并被赋值给枚举对象的 Current 属性。
79
80n 迭代器体的执行将被挂起。所有局部变量的值和参数(包括 this )被保存,该 yield return 语句的位置也被保存。如果 yield return 语句在一个或多个 try 块之内,与之关联的 finally 块在此时将不会执行。
81
82n 枚举器对象的状态被改为 suspended 。
83
84n MoveNext 方法对调用方返回 true ,表明迭代器成功前进到下一个值。
85
86l 当遇到 yield break 语句时,将会发生如下情况
87
88n 如果 yield break 语句在一个或多个 try 块之内,与之关联的 finally 语句将被执行。
89
90n 枚举器对象的状态被改为 after 。
91
92n MoveNext 方法对调用方返回 false ,表明迭代已经完成。
93
94l 当遇到迭代器块的结束点时,将会发生如下情况。
95
96n 枚举器对象的状态被改为 after 。
97
98n MoveNext 方法对调用方返回 false ,表明迭代已经完成。
99
100
101
102
103l 当一个异常被抛出并被传播到迭代器块之外时,将会发生如下情况。
104
105n 在迭代器块之内将会由于异常传播 (exception propagation) 而执行合适的 finally 块。
106
107n 枚举器对象的状态被改为 after 。
108
109n 对于 MoveNext 方法的调用方来说,异常传播将会继续。
110
111### 22.2.2 Current 属性
112
113枚举器对象的 Current 属性受到迭代器块的 yield return 语句的影响。
114
115当枚举器对象处于 suspended 状态时, Current 的值就是最后一次调用 MoveNext 时被设置的值。当枚举器对象处于 before 、 running 或 after 状态时,访问 Current 的所得结果是未指定的。
116
117对于一个具有非 object 类型的 yield 类型迭代器块,通过枚举器对象的 IEnumerable 实现访问 Current 所得实现,对应于通过枚举器对象的 IEnumerator<t> 访问 Current 所得实现,并将结果转换到 object 类型。
118
119### 22.2.3 Dispose 方法
120
121Dispose 方法通过将枚举器对象的状态置为 after ,以清理迭代结果。
122
123l 如果枚举器对象的状态是 before ,调用 Dispose 将改变其状态为 after 。
124
125l 如果枚举器对象的状态是 running ,调用 Dispose 的结果是为指定的。
126
127l 如果枚举器对象的状态是 suspended ,调用 Dispose 将
128
129n 改变其状态为 running 。
130
131n 执行 finally 块,就好像最后执行的 yield return 语句是一个 yield break 语句。如果这里引发一个异常被抛出并传播到迭代器体之外,枚举器对象的状态将被置为 after ,并且该异常将被传播给 Dispose 方法的调用方。
132
133n 改变其状态为 after 。
134
135l 如果枚举器对象的状态为 after ,调用 Dispose 没有效果。
136
137**
138**
139
140## 22.3 可枚举对象
141
142当返回一个可枚举接口类型的函数成员使用迭代器块实现时,调用函数成员不会立即执行迭代器块代码。相反,一个 **可枚举对象(** ** enumerable object ** ** ) ** 将被创建并返回。可枚举对象的 GetEnumerator 方法返回一个枚举器对象,它封装了在迭代器块中指定的代码,当枚举器对象的 MoveNext 方法被调用时,将触发迭代器块代码的执行。可枚举对象具有如下特征。
143
144l 它实现了 IEnumerable 和 IEnumerable<t> 接口,这里 T 是迭代器块的产生类型( yield type )。
145
146l 它使用实参值的拷贝进行初始化(如果有的话),并将实例值传递给函数成员。
147
148可枚对象通常是一个由编译器生成的可枚举类的实例,该类封装了迭代器块的代码,并实现了可枚举接口,但其他实现方法也是可以的。如果可枚举类由编译器生成,该类将内嵌在包含函数成员的类中,并具有私有可访问性,以及一个为编译器所保留使用的名字 (§2.4.2) 。
149
150可枚对象可以实现比在此说明的更多接口。特别的是,可枚举对象也可以实现 IEnumerator 和 IEnumerator<t> 接口,这使得它既可以作为一个可枚举对象又可作为枚举器对象。在那个实现类型中,对可枚举对象的 GetEnumerator 方法的首次调用,将返回可枚举对象自身。对于</t></t></t></t></t></t></t></t></t></t></t>