22.4 yield 语句
yield 语句用于迭代器块以产生一个枚举器对象值,或表明迭代的结束。
embedded-statement: (嵌入语句)
...
yield-statement ( yield 语句)
yield-statement: ( yield 语句)
yield return expression ;
yield break ;
为了确保和现存程序的兼容性, yield 并不是一个保留字,并且 yield 只有在紧邻 return 或 break 关键词之前才具有特别的意义。而在其他上下文中,它可以被用作标识符。
yield 语句所能出现的地方有几个限制,如下所述。
l yield 语句出现在方法体、运算符体和访问器体之外时,将导致编译时错误。
l yield 语句出现在匿名方法之内时,将导致编译时错误。
l yield 语句出现在 try 语句的 finally 语句中时,将导致编译时错误。
l yield return 语句出现在包含 catch 子语句的任何 try 语句中任何位置时,将导致编译时错误。
如下示例展示了 yield 语句的一些有效和无效用法。
delegate IEnumerable
1<int> D();
2
3IEnumerator<int> GetEnumerator() {
4try {
5yield return 1; // Ok
6yield break; // Ok
7}
8finally {
9yield return 2; // 错误 , yield 在 finally 中
10yield break; // 错误 , yield 在 finally 中
11}
12
13
14
15
16try {
17yield return 3; // 错误 , yield return 在 try...catch 中
18yield break; // Ok
19}
20catch {
21yield return 4; // 错误 , yield return 在 try...catch 中
22yield break; // Ok
23}
24
25D d = delegate {
26yield return 5; // 错误 , yield 在匿名方法中
27};
28}
29
30int MyMethod() {
31yield return 1; // 错误 , 迭代器块的错误返回类型
32}
33
34从 yield return 语句中表达式类型到迭代器的产生类型 (§ 22.1.3 ) ,必须存在隐式转换 (§6.1) 。
35
36yield return 语句按如下方式执行。
37
38l 在语句中给出的表达式将被计算( evaluate ),隐式地转换到产生类型,并被赋给枚举器对象的 Current 属性。
39
40l 迭代器块的执行将被挂起。如果 yield return 语句在一个或多个 try 块中,与之关联的 finally 块此时将不会执行。
41
42l 枚举器对象的 MoveNext 方法对调用方返回 true ,表明枚举器对象成功前进到下一个项。
43
44对枚举器对象的 MoveNext 方法的下一次调用,重新从迭代器块挂起的地方开始执行。
45
46yeld break 语句按如下方式执行。
47
48l 如果 yield break 语句被包含在一个或多个带有 finally 块的 try 块内,初始控制权将转移到最里面的 try 语句的 finally 块。当控制到达 finally 块的结束点后,控制将会转移到下一个最近的 try 语句的 finally 块。这个过程将会一直重复直到所有内部的 try 语句的 finally 块都被执行。
49
50l 控制返回到迭代器块的调用方。这可能是由于枚举器对象的 MoveNext 方法或 Dispose 方法。
51
52由于 yield break 语句无条件的转移控制到别处,所以 yield break 语句的结束点将永远不能到达。
53
54**
55**
56
57### 22.4.1 明确赋值
58
59对于以 yield return expr 形式的 yield return 语句 stmt
60
61l 像 stmt 开始一样,在 expr 的开头变量 v 具有明确的赋值状态。
62
63l 如果在 expr 的结束点 v 被明确赋值,那它在 stmt 的结束点也将被明确赋值;否则,在 stmt 结束点将不会被明确赋值。
64
65## 22.5 实现例子
66
67本节以标准 C# 构件的形式描述了迭代器的可能实现。此处描述的实现基于与 Microsoft C# 编译器相同的原则,但这绝不是强制或唯一可能的实现。
68
69如下 Stack<t> 类使用迭代器实现了 GetEnumerator 方法。该迭代器依序枚举了堆栈中从顶到底的元素。
70
71using System;
72using System.Collections;
73using System.Collections.Generic;
74
75class Stack<t>: IEnumerable<t>
76{
77T[] items;
78int count;
79
80public void Push(T item) {
81if (items == null) {
82items = new T[4];
83}
84else if (items.Length == count) {
85T[] newItems = new T[count * 2];
86Array.Copy(items, 0, newItems, 0, count);
87items = newItems;
88}
89items[count++] = item;
90}
91
92public T Pop() {
93T result = items[--count];
94items[count] = T.default;
95return result;
96}
97
98public IEnumerator<t> GetEnumerator() {
99for (int i = count - 1; i >= 0; --i) yield items[i];
100}
101}
102
103
104
105
106GetEnumerator 方法可以被转换到编译器生成的枚举器类的实例,该类封装了迭代器块中的代码,如下所示。
107
108class Stack<t>: IEnumerable<t>
109{
110...
111
112public IEnumerator<t> GetEnumerator() {
113return new __Enumerator1(this);
114}
115
116class __Enumerator1: IEnumerator<t>, IEnumerator
117{
118int __state;
119T __current;
120Stack<t> __this;
121int i;
122
123public __Enumerator1(Stack<t> __this) {
124this.__this = __this;
125}
126
127public T Current {
128get { return __current; }
129}
130
131object IEnumerator.Current {
132get { return __current; }
133}
134
135public bool MoveNext() {
136switch (__state) {
137case 1: goto __state1;
138case 2: goto __state2;
139}
140i = __this.count - 1;
141__loop:
142if (i < 0) goto __state2;
143__current = __this.items[i];
144__state = 1;
145return true;
146__state1:
147\--i;
148goto __loop;
149__state2:
150__state = 2;
151return false;
152}
153
154
155
156
157public void Dispose() {
158__state = 2;
159}
160
161void IEnumerator.Reset() {
162throw new NotSupportedException();
163}
164}
165在先前的转换中,迭代器块之内的代码被转换成 state machine ,并被放置在枚举器类的 MoveNext 方法中。此外局部变量 i 被转换成枚举器对象的一个字段,因此在 MoveNext 的调用过程中可以持续存在。
166
167下面的例子打印一个简单的从整数 1 到 10 的乘法表。该例子中 FromTo 方法返回一个可枚举对象,并且使用迭代器实现。
168
169using System;
170using System.Collections.Generic;
171
172class Test
173{
174static IEnumerable<int> FromTo(int from, int to) {
175while (from <= to) yield return from++;
176}
177
178static void Main () {
179IEnumerable<int> e = FromTo(1, 10);
180foreach (int x in e) {
181foreach (int y in e) {
182Console.Write("{0,3} ", x * y);
183}
184Console.WriteLine();
185}
186}
187}
188
189FromTo 方法可被转换成编译器生成的可枚举类的实例,该类封装了迭代器块中的代码,如下所示。
190
191using System;
192using System.Threading;
193using System.Collections;
194using System.Collections.Generic;
195
196class Test
197{
198...
199
200static IEnumerable<int> FromTo(int from, int to) {
201return new __Enumerable1(from, to);
202}
203
204
205
206
207class __Enumerable1:
208IEnumerable<int>, IEnumerable,
209IEnumerator<int>, IEnumerator
210{
211int __state;
212int __current;
213int __from;
214int from;
215int to;
216int i;
217
218public __Enumerable1(int __from, int to) {
219this.__from = __from;
220this.to = to;
221}
222
223public IEnumerator<int> GetEnumerator() {
224__Enumerable1 result = this;
225if (Interlocked.CompareExchange(ref __state, 1, 0) != 0) {
226result = new __Enumerable1(__from, to);
227result.__state = 1;
228}
229result.from = result.__from;
230return result;
231}
232
233IEnumerator IEnumerable.GetEnumerator() {
234return (IEnumerator)GetEnumerator();
235}
236
237public int Current {
238get { return __current; }
239}
240
241object IEnumerator.Current {
242get { return __current; }
243}
244
245public bool MoveNext() {
246switch (__state) {
247case 1:
248if (from > to) goto case 2;
249__current = from++;
250__state = 1;
251return true;
252case 2:
253__state = 2;
254return false;
255default:
256throw new InvalidOperationException();
257}
258}
259
260public void Dispose() {
261__state = 2;
262}
263
264void IEnumerator.Reset() {
265throw new NotSupportedException();
266}
267}
268}
269
270这个可枚举类实现了可枚举接口和枚举器接口,这使得它成为可枚举的或枚举器。当 GetEnumerator 方法被首次调用时,将返回可枚举对象自身。后续可枚举对象的 GetEnumerator 调用,如果有的话,都返回可枚举对象的拷贝。因此,每次返回的枚举器都有其自身的状态,改变一个枚举器将不会影响另一个。 Interlocked.CompareExchange 方法用于确保线程安全操作。
271
272from 和 to 参数被转换为可枚举类的字段。由于 from 在迭代器块内被修改,所以引入另一个 __from 字段来保存在每个枚举其中 from 的初始值。
273
274如果当 __state 是 0 时 MoveNext 被调用,该方法将抛出 InvalidOperationException 异常。这将防止没有首次调用 GetEnumerator ,而将可枚举对象作为枚举器而使用的 现象发生。
275
276(C# 2.0 Specification 全文完)
277
278吁....&^%终于敲完,该歇歇了,呵呵,眼睛冒火花**&&^%%...</int></int></int></int></int></int></t></t></t></t></t></t></t></t></t></t></int></int>