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<int> D();
IEnumerator<int> GetEnumerator() { try { yield return 1; // Ok yield break; // Ok } finally { yield return 2; // 錯誤, yield 在finally中 yield break; // 錯誤, yield 在 finally中 }
try { yield return 3; // 錯誤, yield return 在try...catch中 yield break; // Ok } catch { yield return 4; // 錯誤, yield return 在 try...catch中 yield break; // Ok }
D d = delegate { yield return 5; // 錯誤, yield 在匿名方法中 }; }
int MyMethod() { yield return 1; // 錯誤, 迭代器塊的錯誤返回類型 }
從yield return 語句中表達式類型到迭代器的產生類型(§22.1.3),必須存在隱式轉換(§6.1)。
yield return 語句按如下方式執行。
l 在語句中給出的表達式將被計算(evaluate),隱式地轉換到產生類型,并被賦給枚舉器對象的Current屬性。
l 迭代器塊的執行將被掛起。如果yield return 語句在一個或多個try塊中,與之關聯的finally塊此時將不會執行。
l 枚舉器對象的MoveNext方法對調用方返回true,表明枚舉器對象成功前進到下一個項。
對枚舉器對象的MoveNext方法的下一次調用,重新從迭代器塊掛起的地方開始執行。
yeld break 語句按如下方式執行。
l 如果yield break 語句被包含在一個或多個帶有finally塊的try塊內,初始控制權將轉移到最里面的try語句的finally塊。當控制到達finally塊的結束點后,控制將會轉移到下一個最近的try語句的finally塊。這個過程將會一直重復直到所有內部的try語句的finally塊都被執行。
l 控制返回到迭代器塊的調用方。這可能是由于枚舉器對象的MoveNext方法或Dispose方法。
由于yield break語句無條件的轉移控制到別處,所以yield break語句的結束點將永遠不能到達。
22.4.1明確賦值 對于以yield return expr 形式的yield return 語句stmt
l 像stmt開始一樣,在expr的開頭變量v具有明確的賦值狀態。
l 如果在expr的結束點v被明確賦值,那它在stmt的結束點也將被明確賦值;否則,在stmt結束點將不會被明確賦值。
22.5實現例子 本節以標準C#構件的形式描述了迭代器的可能實現。此處描述的實現基于與Microsoft C#編譯器相同的原則,但這絕不是強制或唯一可能的實現。
如下Stack<T>類使用迭代器實現了GetEnumerator方法。該迭代器依序枚舉了堆棧中從頂到底的元素。
using System; using System.Collections; using System.Collections.Generic;
class Stack<T>: IEnumerable<T> { T[] items; int count;
public void Push(T item) { if (items == null) { items = new T[4]; } else if (items.Length == count) { T[] newItems = new T[count * 2]; Array.Copy(items, 0, newItems, 0, count); items = newItems; } items[count++] = item; }
public T Pop() { T result = items[--count]; items[count] = T.default; return result; }
public IEnumerator<T> GetEnumerator() { for (int i = count - 1; i >= 0; --i) yield items[i]; } }
GetEnumerator方法可以被轉換到編譯器生成的枚舉器類的實例,該類封裝了迭代器塊中的代碼,如下所示。
class Stack<T>: IEnumerable<T> { ...
public IEnumerator<T> GetEnumerator() { return new __Enumerator1(this); }
class __Enumerator1: IEnumerator<T>, IEnumerator { int __state; T __current; Stack<T> __this; int i;
public __Enumerator1(Stack<T> __this) { this.__this = __this; }
public T Current { get { return __current; } }
object IEnumerator.Current { get { return __current; } }
public bool MoveNext() { switch (__state) { case 1: goto __state1; case 2: goto __state2; } i = __this.count - 1; __loop: if (i < 0) goto __state2; __current = __this.items[i]; __state = 1; return true; __state1: --i; goto __loop; __state2: __state = 2; return false; }
public void Dispose() { __state = 2; }
void IEnumerator.Reset() { throw new NotSupportedException(); } } 在先前的轉換中,迭代器塊之內的代碼被轉換成state machine,并被放置在枚舉器類的MoveNext方法中。此外局部變量i被轉換成枚舉器對象的一個字段,因此在MoveNext的調用過程中可以持續存在。
下面的例子打印一個簡單的從整數1到10的乘法表。該例子中FromTo方法返回一個可枚舉對象,并且使用迭代器實現。
using System; using System.Collections.Generic;
class Test { static IEnumerable<int> FromTo(int from, int to) { while (from <= to) yield return from++; }
static void Main() { IEnumerable<int> e = FromTo(1, 10); foreach (int x in e) { foreach (int y in e) { Console.Write("{0,3} ", x * y); } Console.WriteLine(); } } }
FromTo方法可被轉換成編譯器生成的可枚舉類的實例,該類封裝了迭代器塊中的代碼,如下所示。
using System; using System.Threading; using System.Collections; using System.Collections.Generic;
class Test { ...
static IEnumerable<int> FromTo(int from, int to) { return new __Enumerable1(from, to); }
class __Enumerable1: IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator { int __state; int __current; int __from; int from; int to; int i;
public __Enumerable1(int __from, int to) { this.__from = __from; this.to = to; }
public IEnumerator<int> GetEnumerator() { __Enumerable1 result = this; if (Interlocked.CompareExchange(ref __state, 1, 0) != 0) { result = new __Enumerable1(__from, to); result.__state = 1; } result.from = result.__from; return result; }
IEnumerator IEnumerable.GetEnumerator() { return (IEnumerator)GetEnumerator(); }
public int Current { get { return __current; } }
object IEnumerator.Current { get { return __current; } }
public bool MoveNext() { switch (__state) { case 1: if (from > to) goto case 2; __current = from++; __state = 1; return true; case 2: __state = 2; return false; default: throw new InvalidOperationException(); } }
public void Dispose() { __state = 2; }
void IEnumerator.Reset() { throw new NotSupportedException(); } } }
這個可枚舉類實現了可枚舉接口和枚舉器接口,這使得它成為可枚舉的或枚舉器。當GetEnumerator方法被首次調用時,將返回可枚舉對象自身。后續可枚舉對象的GetEnumerator調用,如果有的話,都返回可枚舉對象的拷貝。因此,每次返回的枚舉器都有其自身的狀態,改變一個枚舉器將不會影響另一個。Interlocked.CompareExchange方法用于確保線程安全操作。
from和to參數被轉換為可枚舉類的字段。由于from在迭代器塊內被修改,所以引入另一個__from字段來保存在每個枚舉其中from的初始值。
如果當__state是0時MoveNext被調用,該方法將拋出InvalidOperationException異常。這將防止沒有首次調用GetEnumerator,而將可枚舉對象作為枚舉器而使用的現象發生。
(C# 2.0 Specification 全文完)
|