List<T>をGetEnumerator()して得られるEnumeratorは、クラスじゃなくて構造体!!!
入れ子集合モデルから木構造を作るのプログラムを書いてるときに、思ったとおりに動かなくて困ったので、そのメモ。
型を、List<T>にするかIList<T>にするかで挙動がガラッと変わる話です。
やりたかったこと
いろんなメソッドをまたいで、リストの要素を順番に使う必要があった。
→そうだ、IEnumeratorを渡せばいいじゃん!
(インタフェースは参照型なので、メソッド呼び出し先での変更が呼び出し元に反映される)
→呼び出し先で値を消費しても、呼び出し元で反映されない…
void Function1(List<int> data)
{
var dataEnumerator = data.GetEnumerator();
while (dataEnumerator.MoveNext())
{
Console.WriteLine($"Function1: {dataEnumerator.Current}");
// 別の関数でもリストの値を使いたい。
Function2(dataEnumerator);
}
}
void Function2(IEnumerator<int> dataEnumerator)
{
// この中でも、dataEnumerator.MoveNext()を呼び出して、値をいくつか消費する。
for (int i = 0; i < 2; i++)
{
if (!dataEnumerator.MoveNext()) break;
Console.WriteLine($"--Function2: {dataEnumerator.Current}");
}
}
実行する。
Function1(new List<int> { 1, 2, 3 });
↓望む結果
Function1: 1
--Function2: 2
--Function2: 3
↓実際の結果
Function1: 1
--Function2: 2
--Function2: 3
Function1: 2
--Function2: 3
Function1: 3
List<T>のGetEnumerator、特殊な実装がされていた…
速度のため、ですね?
IEnumerable<T>のGetEnumeratorはIEnumeratorを返すけど、List<T>とかStack<T>とかは、独自のEnumerator(構造体)を返すようになっていた。同じ関数名だから引っかかった…
var dataEnumerator = data.GetEnumerator();
これで、返ってくる型がIEnumerator<T>だと思い込んでた失敗でもある。
解決方法
すなわち、先にList<T>をIEnumerable<T>に変換しておくとか、構造体のEnumeratorをIEnumerator<T>の変数で受けるとかすれば、ボクシングされてうまくいく。
入れ子集合モデルから木構造を作るでは、前者の方法、仮引数でList<T>じゃなくてIEnumerable<T>を受け取るようにした。実際に欲しい機能は、IEnumerable<T>インタフェースだからね。
今回はIEnumerable<T>にすることで解決したんだけど、インタフェースであればいいので、IEnumerable<T>じゃなくてIList<T>にしてもちゃんと思った通りに動く。
List<T>にするかIList<T>にするかで変わっちゃうのは、なかなかですね…