C#2.0語言規範(四)迭代器

來源:互聯網
上載者:User
規範 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()方法而直接將可枚舉對象用作列舉程式。


相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.