【C#】イテレーターの仕組み(コンパイラ展開シミュレート含みます)
イテレーター(反復)動作についてC#では、仕組みが用意されています。
ユーザーは、簡易に使えるように内部で処理をされていますが、今回は、どのようにコードがコンパイラで展開されているのかをみてみましょう
yield returnを2行のみ
ユーザー作成コード
foreach (var item in Collection())
{
Console.WriteLine(item);
}
IEnumerable<int> Collection()
{
yield return 1;
yield return 2;
}
実行結果
仕組みを理解するためにブレークポイントを設定して、ステップ実行でコードの進行を確認してみましょう
上記のコードを実行すると、Collectionメソッド(またはコンパイラによって展開されたCollectionクラス)から生成されるイテレータを通じて反復処理が行われます。このイテレータは、2つの整数値 1 と 2 を順に生成し、それらをコンソールに出力します。
具体的には、実行結果は以下のようになります:
1
2
この結果は、Collectionメソッド内のyield return文が2回実行され、最初に1が、次に2が返されることを示しています。foreachループはこれらの値を受け取り、それぞれの値に対してConsole.WriteLineメソッドを呼び出して、値をコンソールに出力します。
このシンプルな例では、yield returnを使ったイテレーションのメカニズムがどのように機能するか、そしてコンパイラがどのようにこのメカニズムをIEnumerable<T>およびIEnumerator<T>を実装するクラスに展開するかが示されています。
コンパイラが展開したコード
yield returnを使ったイテレータメソッドのコンパイラ展開をクラスレベルで説明すると、C#コンパイラはyield returnを含むメソッドを一種の状態マシンに変換します。この状態マシンは、IEnumerable<T>およびIEnumerator<T>インターフェイスを実装するクラスとして構築されます。ここでは、Collectionメソッドがどのように展開されるかを具体的に見ていきましょう。
Collectionメソッドの簡易版のコンパイラ展開例を示します。このコードは、コンパイラが内部的に生成するコードの概念的な理解を目的としており、実際のコンパイラ出力とは異なる場合がありますが、yield returnの背後にあるメカニズムを理解するのに役立ちます。
using System.Collections;
class Program
{
static void Main(string[] args)
{
foreach (var item in new Collection())
{
Console.WriteLine(item);
}
}
}
// コンパイラによって生成されるイテレータの概念的な展開
public class Collection : IEnumerable<int>, IEnumerator<int>
{
private int state = 0;
private int current;
public int Current => current;
object IEnumerator.Current => Current;
public Collection() { }
public bool MoveNext()
{
switch (state)
{
case 0:
current = 1;
state = 1;
return true;
case 1:
current = 2;
state = 2;
return true;
case 2:
return false;
default:
return false;
}
}
public void Reset()
{
throw new NotImplementedException();
}
public void Dispose() { }
public IEnumerator<int> GetEnumerator()
{
// 通常、新しいイテレーションごとに新しいイテレータインスタンスを返す必要があります。
// しかし、ここではシンプルさのために、このインスタンス自身を返します。
// 実際の展開では、各GetEnumerator呼び出しで新しいインスタンスが生成されます。
return this;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
yield returnをfor文で繰り返す
ユーザー作成コード
foreach (var item in Collection())
{
Console.WriteLine(item);
}
IEnumerable<int> Collection()
{
for (int i = 0; i < 10; i++)
{
yield return i;
}
}
実行結果
上記のコードを実行すると、コンソールに0から9までの数字が一行ずつ出力されます。Collectionメソッドは、0から9までの整数を生成し、それぞれの数値がforeachループによって取り出され、Console.WriteLine(item);によってコンソールに表示されます。
具体的な実行結果は以下の通りです:
0
1
2
3
4
5
6
7
8
9
この出力は、Collectionメソッドがイテレータパターンを利用して内部的に状態を管理しながら、0から始まり9で終わる連続した整数を順番に生成し、それらがforeachループによって一つずつ取り出されていることを示しています。Console.WriteLine(item);はこれらの各数値をコンソールに出力します。
コンパイラが展開したコード
foreachループとyield returnを使用するコードをコンパイラがどのように展開するかのイメージを示すには、まずforeachループが内部的にどのように動作するかを理解する必要があります。yield returnを使用すると、C# コンパイラは自動的に状態マシンを生成します。この状態マシンは、イテレーションごとに次の値を返す方法を管理します。
以下は、指定されたコードがコンパイラによってどのように展開されるかのイメージです。クラスの展開も含めています。
using System;
using System.Collections;
using System.Collections.Generic;
internal class Program
{
private static void Main(string[] args)
{
// コンパイラがforeachループを展開した形
using (IEnumerator<int> enumerator = Collection().GetEnumerator())
{
while (enumerator.MoveNext())
{
var item = enumerator.Current;
Console.WriteLine(item);
}
}
}
private static IEnumerable<int> Collection()
{
return new CollectionEnumerable();
}
// コンパイラが 'yield return' を使用するメソッドから生成するクラス
private class CollectionEnumerable : IEnumerable<int>, IEnumerator<int>
{
private int state = 0;
private int current;
public int Current => current;
object IEnumerator.Current => Current;
public CollectionEnumerable() { }
public bool MoveNext()
{
switch (state)
{
case 0:
state = 1;
current = -1;
break;
case 1:
break;
default:
return false;
}
if (current < 9)
{
current++;
return true;
}
state = -1; // イテレーションの終了
return false;
}
public void Reset()
{
throw new NotImplementedException();
}
public void Dispose()
{
// 必要に応じてリソース解放
}
public IEnumerator<int> GetEnumerator()
{
if (state == 0 || state == -1)
{
state = 0;
return this;
}
else
{
return new CollectionEnumerable();
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
この例では、Collectionメソッドが生成するIEnumerable<int>とIEnumerator<int>の実装を含むCollectionEnumerableクラスを手動で作成しています。実際には、コンパイラがyield returnを使用するメソッドから生成するクラスは、このように明示的に書かれることはなく、より複雑な命名や追加の最適化が施されることがありますが、基本的な概念としてはこのように理解できます。
コンパイラによるforeachループの展開は、GetEnumerator、MoveNext、そしてCurrentプロパティの呼び出しを直接使ってループを実行する形になります。usingステートメントは、IEnumerator<int>がIDisposableを実装している場合に、ループの終了後にリソースを適切に解放するために使用されます。






ディスカッション
コメント一覧
まだ、コメントがありません