LoginSignup
8
4

More than 3 years have passed since last update.

ListのEnumeratorが構造体ではまった話

Last updated at Posted at 2019-11-11

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>GetEnumeratorIEnumeratorを返すけど、List<T>とかStack<T>とかは、独自のEnumerator(構造体)を返すようになっていた。同じ関数名だから引っかかった…

var dataEnumerator = data.GetEnumerator();

これで、返ってくる型がIEnumerator<T>だと思い込んでた失敗でもある。

解決方法

すなわち、先にList<T>IEnumerable<T>に変換しておくとか、構造体のEnumeratorIEnumerator<T>の変数で受けるとかすれば、ボクシングされてうまくいく。
入れ子集合モデルから木構造を作るでは、前者の方法、仮引数でList<T>じゃなくてIEnumerable<T>を受け取るようにした。実際に欲しい機能は、IEnumerable<T>インタフェースだからね。

今回はIEnumerable<T>にすることで解決したんだけど、インタフェースであればいいので、IEnumerable<T>じゃなくてIList<T>にしてもちゃんと思った通りに動く。
List<T>にするかIList<T>にするかで変わっちゃうのは、なかなかですね…

8
4
7

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4