4.1 迭代器塊 一個迭代器塊(iterator block)是一個能夠產生有序的值序列的塊。迭代器塊和普通語句塊的區別就是其中出現的一個或多個yield語句。
yield return語句產生迭代的下一個值。 yield break語句表示迭代完成。 只要相應的函數成員的返回值類型是一個枚舉器接口(見4.1.1)或是一個可枚舉接口(見4.1.2),一個迭代器塊就可以用作方法體、運算符體或訪問器體。
迭代器塊并不是C#語法中的獨立元素。它們受多種因素的制約,并且對函數成員聲明的語義有很大影響,但在語法上它們只是塊(block)。
當一個函數成員用一個迭代器塊來實現時,如果函數成員的形式參數列表指定了ref或out參數,則會引起編譯錯誤。
如果在迭代器塊中出現了return語句,則會因此編譯錯誤(但yield return語句是允許的)。
如果迭代器塊包含不安全上下文,則會引起編譯錯誤。一個迭代器塊必須定義在一個安全的上下文中,即使它的聲明嵌套在一個不安全上下文中。
4.1.1 枚舉器接口 枚舉器(enumerator)接口包括非泛型接口System.Collections.IEnumerator和泛型接口System.Collections.Generic.IEnumerator<T>的所有實例化。在這一章中,這些接口將分別稱作IEnumerator和IEnumerator<T>。
4.1.2 可枚舉接口 可枚舉(enumerable)接口包括非泛型接口System.Collections.IEnumerable和泛型接口System.Collections.Generic.IEnumerable<T>。在這一章中,這些接口分別稱作IEnumerable和IEnumerable<T>。
4.1.3 生成類型 一個迭代器塊能夠產生一個有序的值序列,其中所有的制具有相同的類型。這個類型稱為迭代器塊的生成類型(yield type)。
用于實現一個返回IEnumerator或IEnumerable的函數成員的迭代器塊的生成類型為object。 用于是下一個返回IEnumerator<T>或IEnumerable<T>的函數成員的迭代器塊的生成類型為T。 4.1.4 this訪問 在一個類的一個實例成員中的迭代器塊里,表達式this是一個值。這個值的類型就是出現這種用法的類,并且它是對被調用的方法所在的對象的一個引用。
在一個結構的一個實例成員中的迭代器塊里,表達式this是一個變量。這個變量的類型就是出現這種用法的結構。這個變量存貯了對被調用成員所在結構的一個拷貝。結構的實例成員中的迭代器塊里的this變量和以該結構為類型的值變量完全一樣。
4.2 Enumerator對象 如果一個函數成員使用了迭代器塊來返回一個枚舉器接口類型,對該函數成員的調用不會立即執行迭代器塊中的代碼,而是建立并返回一個枚舉器對象。這個對象封裝了迭代器塊中指定的代碼,而對迭代器中指定的代碼的執行發生在調用該枚舉器對象的MoveNext()方法時。一個枚舉器對象具有如下特征:
它實現了IEnumerator和IEnumerator<T>,這里T是迭代器塊的生成類型。 它實現了System.IDisposable。 它用傳遞給函數成員的參數值(如果有的話)和實例值進行初始化。 它有四個可能的狀態:before、running、suspended和after,其初始狀態為before。 典型的枚舉器對象是由編譯器自動生成的封裝了迭代器塊中的代碼并實現了枚舉器接口的枚舉器類的實例,但其他的實現也是允許的。如果一個枚舉器類是由編譯器自動生成的,則該類是直接或間接地嵌套在函數成員中的,具有私有的可訪問性,并且具有一個由編譯器保留使用的名字。
一個枚舉器對象可以實現上面所述之外的其它接口。
后面的幾節詳細地描述了由一個枚舉器對象所實現的IEnumerable和IEnumerable<T>接口中的MoveNext()、Current和Dispose()成員的確切行為。
注意,枚舉器對象不支持IEnumerator.Reset()方法。調用該方法會拋出System.NotSupporteException異常。
4.2.1 MoveNext()方法 枚舉器對象的MoveNext()方法封裝了迭代器塊的代碼。對MoveNext()方法的調用執行了迭代器塊中的代碼,并為枚舉器對象的Current屬性設置一個適當的值。MoveNext()方法完成得確切動作取決于調用MoveNext()方法是枚舉器對象的狀態:
如果枚舉器對象的狀態為before,調用MoveNext()方法: 將狀態設置為running。 將迭代器塊對象的參數(包括this)初始化為枚舉器對象初始化時所保存的變量值和實例值。 從執行迭代器塊的開始執行,直到被中斷(將在下面討論)。 如果枚舉器對象的狀態為running,則調用MoveNext()方法的結果未指定。 如果枚舉器對象的狀態為suspended,調用MoveNext()方法: 將狀態設置為running。 將所有局部變量和參數(包括this)恢復為迭代器塊執行過程中最后一次掛起時所保存的值。注意這些變量所引用的對象的內容在上一次MoveNext()后可能會發生改變。 從上一次執行中斷所在的yield return語句繼續執行迭代器塊,直到執行再次被中斷(將在下面討論)。 如果枚舉器對象的狀態為after,調用MoveNext()方法將返回false。 當MoveNext()方法執行迭代器塊時,執行過程會通過四種途徑中斷:yield return語句、yield break語句、遇到迭代器塊的結尾以及迭代器塊中拋出了異常并被傳播到塊外。
當遇到yield return語句(見4.4)時: 對語句中給定的表達式進行求值,隱式轉換為生成類型,并賦給枚舉器對象的Current屬性。 掛起迭代器體的執行過程。保存所有局部變量和參數(包括this)的值,以及這個yield return語句的位置。如果該yield return語句位于一個或多個try塊中,則與之相關聯的finally塊在此時還不會被執行。 將枚舉器對象的狀態設置為suspended。 向MoveNext()方法的調用者返回true,表示迭代已經成功地轉移到下一個值上。 當遇到yield break語句(見4.4)時: 如果該yield break語句位于一個或多個try塊中,則執行與之相關聯的finally塊。 將枚舉器對象的狀態設置為after。 向MoveNext()方法的調用者返回false,表示迭代完成。 當遇到迭代器快的結尾時: 將枚舉器對象的狀態設置為after。 向MoveNext()方法的調用者返回false,表示迭代完成。 當迭代器快報出了一個異常,并傳播到塊外時: 迭代器塊中適當的finally塊將被執行。 將枚舉器對象的狀態設置為after。 將異常傳播給MoveNext()方法的調用者。 4.2.2 Current屬性 一個枚舉器對象的Current屬性受迭代器塊中的yield return語句的影響。
當一個枚舉器對象處于suspended狀態時,Current屬性的值由最后一次對MoveNext()方法的調用設置。當一個枚舉器對象處于before、running或after狀態時,訪問Current屬性的結果是未定義的。
如果一個迭代器塊的生成類型不是object,通過枚舉器對象實現的IEnumerable以及相應的IEnumerator<T>對Current的訪問會將結果轉換為object。
4.2.3 Dispose()方法 Dispose()方法通過將枚舉器對象的狀態設置為after來清除迭代器。
如果枚舉器對象的狀態為before,調用Dispose()方法將其狀態設置為after。 如果枚舉器對象的狀態為running,調用Dispose()方法的結果是未定義的。 如果枚舉器對象的狀態為suspended,調用Dispose()方法: 將狀態設置為running。 執行所有的finally塊,好像yield return語句是yield break語句一樣。如果這導致了異常被拋出并傳播到迭代器塊外,則將枚舉器對象的狀態設置為after并將異常傳播給Dispose()方法的調用者。 將狀態設置為after。 如果枚舉器對象的狀態為after,調用Dispose()方法沒有任何效果。 4.3 Enumerable對象 當一個返回一個可枚舉接口類型的函數成員使用了迭代器塊時,對該函數成員的調用不會立即執行迭代器塊中的代碼,而是建立并返回一個可枚舉對象。該可枚舉對象有一個GetEnumerator()方法,能夠返回一個枚舉器對象。該枚舉器對象封裝了迭代器塊中指定的代碼,當調用這個枚舉器對象的MoveNext()方法時,會執行迭代器塊中的代碼。一個可枚舉對象具有如下特征:
它實現了IEnumerable或IEnumerable<T>,這里T是迭代器塊的生成類型。 它用傳遞給函數成員的參數值(如果有的話)和實例值進行初始化。 典型的可枚舉對象是由編譯器自動生成的封裝了迭代器塊中的代碼并實現了可枚舉接口的可枚舉類的實例,但其他的實現也是允許的。如果一個可枚舉類是由編譯器自動生成的,則該類是直接或間接地嵌套在函數成員中的,具有私有的可訪問性,并且具有一個由編譯器保留使用的名字。
一個可枚舉對象可以實現上述之外的其它接口。例如,一個可枚舉對象還可以實現IEnumerator和IEnumerator<T>,使得它既是可枚舉的又是一個枚舉器。這種情況下,當可枚舉對象的GetEnumerator()方法第一次被調用時,將返回可枚舉對象本身。以后對可枚舉對象的GetEnumerator()方法的調用(如果有的話),將返回可枚舉對象的一個拷貝。因此,每個被返回的枚舉器具有其自己的狀態,并且一個枚舉器和其它枚舉器互不影響。
4.3.1 GetEnumerator()方法 一個可枚舉對象提供了對IEnumerator和IEnumberator<T>接口的GetEnumerator()方法的實現。兩個GetEnumerator()方法共享一個實現,能夠獲取并返回一個有效的枚舉器對象。該枚舉器對象使用可枚舉對象被初始化時所保存的參數值和實例值進行初始化,該枚舉器對象的功能如4.2節所描述。
4.4 yield語句 迭代器塊中的yield語句用于生成一個值,或發出一個迭代完成的信號。
embedded-statement: ... yield-statement
yield-statement: yield return expression ; yield break ;
內嵌語句: ... yield語句
yield語句: yield return 表達式 ; yield break ;
為了保證和現有程序的兼容性,yield并不是一個保留字,只有當一個return語句緊隨其后時,yield語句才有這特殊的意義。其它情況下,yield語句可以用作標識符。
yield語句的出現首很多限制,如下所描述:
如果一個yield語句出現在方法體、運算符體或訪問器體之外,則會引起編譯錯誤。 如果一個yield語句出現在匿名方法內部,則會引起編譯錯誤。 如果一個yield語句出現在finally或一個try塊內,則會引起編譯錯誤。 如果一個yield語句出現在一個帶有catch語句的try塊內,則會引起編譯錯誤。 下面的例子展示了一些yield語句的有效的和無效的用法。
delegate IEnumerable<int> D();
IEnumerator<int> GetEnumerator() { try { yield return 1; // 正確 yield break; // 正確 } finally { yield return 2; // 錯誤,yield出現在finally塊中E yield break; // 錯誤,yield出現在finally塊中 }
try { yield return 3; // 錯誤,yield return語句出現在try...catch語句中 yield break; // 正確 } catch { yield return 4; // 錯誤,yield return語句出現在try...catch語句中 yield break; // 正確 }
D d = delegate { yield return 5; // 錯誤,yield語句出現在匿名方法中 }; }
int MyMethod() { yield return 1; // 錯誤,迭代器塊具有錯誤的返回值類型 }
從yield return語句中的表達式的類型到迭代器塊的生成類型(見4.1.3)必存在一個隱式轉換。
yield return語句依照下面的步驟執行:
對語句中給定的表達式進行求值,并隱式轉換為生成類型,然后賦給枚舉器對象的Current屬性。 掛起對迭代器塊的執行。如果該yield return語句位于一個或多個try塊中,相應的finally塊暫時不會被執行。 MoveNext()方法向其調用者返回true,表示枚舉器對象成功地前進到下一個值上。 對枚舉器對象的MoveNext()方法的下一次調用將從上一次掛起的地方恢復對迭代器塊的執行。
yield break語句依照下面的步驟執行:
如果yield break語句位于一個或多個帶有finally塊的try塊中,控制將被轉移到最里面的try塊對應的finally塊中。當控制流程遇到finally塊的結尾(如果能夠的話),控制將被轉移到外一層try塊對應的finally塊中。這個過程持續到所有try語句對應的finally塊都被執行完。 將控制返回給迭代器塊的調用者。這可能從MoveNext()方法或Dispose()方法中返回。 由于一個yield break語句無條件地將控制轉移到其它地方,因此一個yield break的終點將永遠不可達。
4.4.1 明確賦值 對于下面形式的yield return語句:
yield return expr ;
對于一個變量v,在expr的開始處和語句的開始處有同樣的明確賦值。 如果一個變量v在expr的結束處被明確賦值,則它是在語句的結尾被明確賦值的;否則,它未在語句的結尾被明確賦值。 4.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(); } } }
上面的轉換中,迭代器塊中的代碼被轉換為狀態機并放在枚舉器類的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.CoompareExchange()方法可以用于確保線程安全。
from和to參數被轉換為可枚舉類的域。因為迭代器塊改變了from,因此引入了一個附加的__from域來保存每個枚舉器中的from的初始值。
當__state是0時,MoveNext()方法將跑出一個InvalidOperationException異常。這將保證不會發生沒有首先調用GetEnumerator()方法而直接將可枚舉對象用
|