tonari note

オンラインゲームエンジニアの雑記

C#のforeachな話

foreachについて最近よく聞かれるので。

var ary = new []{1, 2, 3};
foreach(var num in ary)
	Console.WriteLine(num);

配列やらリストやらを順番に処理していく。

この順番にっていうのがミソで、じゃあ順番ってなんやねんってなるわけです。

var ary = new []{1, 2, 3};
var it = ary.GetEnumerator();
while(it.MoveNext())
	Console.WriteLine(it.Current);

実行結果

こんな感じでIEnumeratorやらIEnumerator(T)やらを貰って、MoveNextメソッドを呼び、次の要素があるならその要素を取得する、みたいなことをやってます。

世間ではシーケンスやら言ったりしますが、配列なりListなりDictionaryなりは全部IEnumerableIEnumerable(T)やらを実装してます。
これらIEnumerableの定義はGetEnumerator()メソッドだけです。
つまり配列やリストなどじゃなくても、自前のクラスでこれらを実装すればforeachでなめることができます。

class MySequence : IEnumerable
{
	public IEnumerator GetEnumerator()
	{
		return new MyEnumerator();
	}

	class MyEnumerator : IEnumerator
	{
		private int count = 0;

		public object Current
		{
			get { return (count++).ToString(); }
		}

		public bool MoveNext()
		{
			return count < 3;
		}

		public void Reset()
		{
		}
	}
}

実行結果

ここで面白いのは、C#のforeachは糖衣構文なので、IEnumerableを実装していなくても、IEnumerator GetEnumerator()があれば動くということです。
実行結果
先ほどと違ってMySequenceはIEnumerableを実装していません。
LINQの場合はIEnumerable(T)の拡張メソッドが主になりますが、IEnumerableの拡張メソッドも使われたくない、けどforeachでは使いたいって言う時は上記の手段で解決できます。

さて、foreachにはもう一つ機能があります。
実行結果
EnumeratorにIDisposableを実装すると、エラーが起こった際にそのDisposeを呼んでくれます。

C# 5.0以降では、Unityで使われているC# 3.0と挙動が変わる部分があります。
そちらは岩永さんの所に詳しく書かれていますのでこちらへ。
C#5.0の新機能

foreachは基本的にはwhileに展開されるだけの糖衣構文なのでインデックスが取れない(LINQのSelect使うと取れる)んですが、それなりに便利だと思うので、良い感じに使いたいですぬ。