首頁 >後端開發 >C#.Net教程 >C# 2.0 Specification(迭代器)(二)

C# 2.0 Specification(迭代器)(二)

黄舟
黄舟原創
2017-01-03 13:04:291217瀏覽

22.4 yield 語句

yield語句用於迭代器區塊以產生一個枚舉器物件值,或表示迭代的結束。
embedded-statement:(嵌入語句)
...
yield-statement(yield語句)
yield-statement:(yield 語句)
yield return expression ;
yield breakbreak並不是一個保留字,而且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類別使用迭代器實作了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 全文完)


以上就是C# 2.0 Specification(迭代器)(二)的内容,更多相关内容请关注PHP中文网(www.php.cn)!


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn