C# 2.0 仕様 (イテレータ) (2)

黄舟
黄舟オリジナル
2017-01-03 13:04:291207ブラウズ

22.4 yield ステートメント

yield ステートメントは、列挙子オブジェクト値を生成するか、反復の終了を示すために反復子ブロック内で使用されます。
embedded-statement: (埋め込みステートメント)
...
yield-statement (yieldステートメント)
yield-statement: (yieldステートメント)
yield return式 ;
yield Break ;
既存のプログラムとの互換性を確保するために、yieldは予約語ではなく、yield は return または Break キーワードの直前にのみ特別な意味を持ちます。他のコンテキストでは、識別子として使用できます。
以下で説明するように、yield ステートメントを表示できる場所にはいくつかの制限があります。
l yield ステートメントがメソッド本体、演算子本体、およびアクセサー本体の外側にある場合、コンパイル時エラーが発生します。
l 匿名メソッド内に yield ステートメントが出現すると、コンパイル時エラーが発生します。
l try ステートメントのfinally ステートメントに yield ステートメントが出現すると、コンパイル時エラーが発生します。
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 ステートメントの式の型から反復子の yield 型 (§22.1.3) への暗黙的な変換 (§6.1) が必要です。
yield return ステートメントは次のように実行されます。
l ステートメントで指定された式は評価され、暗黙的に生成された型に変換され、列挙子オブジェクトの Current プロパティに割り当てられます。
l イテレータブロックの実行は一時停止されます。 yield return ステートメントが 1 つ以上の try ブロック内にある場合、この時点では関連する Final ブロッ​​クは実行されません。
l 列挙子オブジェクトの MoveNext メソッドは呼び出し元に true を返し、列挙子オブジェクトが次の項目に正常に進むことを示します。

列挙子オブジェクトの MoveNext メソッドへの次の呼び出しでは、反復子ブロックが中断された場所から実行が再開されます。
yeld Break文は以下のように実行されます。
l yield Break ステートメントが、finally ブロックを含む 1 つ以上の 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 の先頭と同様に、変数 v は expr の先頭で明示的な代入ステータスを持ちます。
l expr の終点で v が明示的に割り当てられている場合、stmt の終点でも明示的に割り当てられます。それ以外の場合、stmt の終点では明示的に割り当てられません

22.5 実装例

このセクション標準 C# を使用します。 コンポーネントの形式は、イテレータの可能な実装を記述します。ここで説明する実装は Microsoft C# コンパイラと同じ原則に基づいていますが、これは決して必須の実装でも、唯一可能な実装でもありません。
次の Stack8742468051c85b06f0a0af9e3e506b5c クラスは、反復子を使用して 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.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 までご連絡ください。