C# 2.0 Specification(迭代器)(二)
2024-07-21 02:20:07
供稿:網友
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 全文完)