.NET & Control > C# 反復子でコレクション作り
のぶ亭『プログラミングの相談窓口』 … 様々なプログラミング問題を個別対応致します |
反復子でコレクション作り
反復子はメソッド、get アクセサ、演算子のいずれかで、yieldキーワードを使用して配列またはコレクション クラスに対してカスタム設定した反復を実行します。
yield return ステートメントを使用すると、ソース シーケンス内の要素は、次の要素へのアクセスの前に、直ちに呼び出し元に戻されます。反復子をメソッドとして記述しても、コンパイラが入れ子のクラス、つまり事実上ステート マシンに変換します。
このクラスは、クライアント コードで foreach ループが続く限り、反復子の位置を追跡します。
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
反復子は、foreachステートメントを使用してクライアントコード
から呼び出されます。
たとえば、逆の順番で要素を戻すクラス、または反復子が要素を
戻す前に各要素で操作を実行するクラス用の反復子を作成できます。
クラスまたは構造体用の反復子を作成する場合、IEnumeratorイン
ターフェイス全体を実装する必要はありません。
コンパイラは、反復子を検出すると、IEnumeratorインターフェイス
またはIEnumerator<(Of <(T>)>)インターフェイスの Currentメソ
ッド、MoveNext
メソッド、および Dispose メソッドを自動的に
生成します。
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
反復子の概要
- 反復子は、順序付けされた同じ型の一連の値を返すコードのセクションです。
- 反復子は、メソッド、演算子、または get アクセサの本体として使用できます。
- 反復子コードでは、yield return ステートメントを使用して、各要素を順に返します。反復処理は、yield break で終了します。
- 複数の反復子を 1 つのクラスに実装できます。各反復子は、他のクラス メンバと同様に一意の名前を持つ必要があり、"foreach(int x in SampleClass.Iterator2){}" のように、foreach ステートメント内でクライアント コードによって呼び出すことができます。
- 反復子の戻り値の型は、IEnumerable、IEnumerator、
IEnumerable<(Of <(T>)>)、または IEnumerator<(Of <(T>)>)である必要があります。 - 反復子は、LINQ クエリの遅延実行動作の基礎になります。
yield キーワードを使用して、単数または複数の戻り値を指定します。 yield return ステートメントに到達すると、現在の位置が保存されます。 次回、反復子が呼び出されると、この位置から実行が再開されます。反復子は、特にコレクション クラスで役立ち、バイナリ ツリーなどの複雑なデータ構造体を簡単に反復処理できるようにします。
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
01: public class DaysOfTheWeek : System.Collections.IEnumerable 02: { 03: string[] days = { "Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat" }; 04: 05: public System.Collections.IEnumerator GetEnumerator() 06: { 07: for (int i = 0; i < days.Length; i++) 08: { 09: yield return days[i]; 10: } 11: } 12: } 13: 14: class TestDaysOfTheWeek 15: { 16: static void Main() 17: { 18: // Create an instance of the collection class 19: DaysOfTheWeek week = new DaysOfTheWeek(); 20: 21: // Iterate with foreach 22: foreach (string day in week) 23: { 24: System.Console.Write(day + " "); 25: } 26: } 27: } 28: 29: // Output: Sun Mon Tue Wed Thr Fri Sat
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
皆さんは、この解説で理解できましたか?
foreachを使ってデータを取り出すクラスまたはプロパティを簡単に実装するための機能が yieldステートメントです。
注目すべきは次のコードです。
この例では、yield の機能をわかりやすくするため、オブジェクトからメソッドに変えています。
foreachステートメントは、コレクションから順にデータを取り出すステートメントことです。
GetEnumerator()メソッドは、単純に文字列を返すだけですが、yieldステートメントを記述することで、次に呼び出された時、次の要素を返す仕組みが実装できます。
その結果、繰り返し呼び出されると、次の要素を返して次に移動を繰り返します。要素が無くなると終了です。
01: public static System.Collections.IEnumerable GetEnumerator() 02: { 03: yield return "Sun"; 04: yield return "Mon"; 05: yield return "Tue"; 06: yield return "Wed"; 07: yield return "Thr"; 08: yield return "Fri"; 09: yield return "Sat"; 10: } 11: 12: static void Main() 13: { 14: foreach (string day in GetEnumerator()) 15: { 16: System.Console.Write(day + " "); 17: } 18: }
このコードでも、MicrosoftのSampleと同様の結果になります。
通常、このようなコードをyieldステートメントを使わずに実装するにはやっかいでした。それが、たった一言宣言するだけで実現できるのです。
この機能は、応用範囲が色々ありそうですね。
実験 foreach を使わないで直接呼び出すとどうなるのか?
01: System.Console.WriteGetEnumerator() + " "); 02: System.Console.WriteGetEnumerator() + " "); 03: System.Console.WriteGetEnumerator() + " "); 04: System.Console.WriteGetEnumerator() + " "); 05: System.Console.WriteGetEnumerator() + " "); 06: System.Console.WriteGetEnumerator() + " "); 07: System.Console.WriteGetEnumerator() + " "); 08: System.Console.WriteGetEnumerator() + " ");
"TestDaysOfTheWeek+<GetEnumerator>d__0"・・・
というメッセージが出力され、意図した結果にはなりませんでした。
コレクションを呼び出す際は、foreach ステートメントで呼び出す必要があるという結果です。
Microsoftの説明をみると書いてありました。なお、実行によるエラーや例外は発生しません。
こうして、本質が理解できると、Microsoftの解説も理解できるようになり、さらに詳細な解説も読めるようになるのはどうしてでしょうね?