C#でのイテレータパターン

1. Iteratorパターン 1 | TECHSCORE(テックスコア)

これをC#で書く。イテレータC#だとIEnumerator<T>なので、イテレータパターンはIEnumerable<T>を実装するパターンということになる。

public class MyStudentList : StudentList, IEnumerable<Student>
{
    public MyStudentList() { }

    public MyStudentList(int studentCount): base(studentCount) { }

    public IEnumerator<Student> GetEnumerator()
    {
        return new MyStudentListIterator(this);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

public class MyStudentListEnumarator : IEnumerator<Student>
{
    private MyStudentList myStudentList;
    private int index;
    private int startIndex;

    public MyStudentListEnumarator(MyStudentList list)
    {
        this.myStudentList = list;
        this.index = -1;
        this.startIndex = 0;
    }

    public Student Current
    {
        get
        {
            return myStudentList.GetStudentAt(index);
        }
    }

    object IEnumerator.Current
    {
        get
        {
            return Current;
        }
    }

    public bool MoveNext()
    {
        if (myStudentList.GetLastNum() > ++index)
        {
            return true;
        }

        index = myStudentList.GetLastNum();
        return false;
    }

    public void Reset()
    {
        index = startIndex - 1;
    }

    public void Dispose() { }
}

サンプルの通りに書くとこんな感じになる。

要は、IEnumerable<T>を実装していないStudentListはクソなので、継承してIEnumerable<T>を追加実装しちゃいましょう、と。

ここでIEnumerator<T>ってどうやって実装するんだろうとReference Source見てみたんだけど、まちまちで面白い。MoveNext()Currentの順で呼んだときにうまく動けばよいみたいで、List<T>.Enumeratorでは

  1. MoveNext()で今のインデックスの値を取っておいてからインデックスを進める
  2. Currentで取得済みの値を返す

Reference Source

という処理になっている一方、ArrayEnumeratorでは

  1. MoveNext()でインデックスを進める
  2. Currentで今のインデックスの値を返す

Reference Source

となっている。

私は後者の方が直感的だと思ったので、上記ではそのように実装。

ところで、IEnumerator<T>を実装してみるのは勉強になって面白いが、C#にはyield returnという便利なものがあるので通常はこんな面倒な実装はしなくて良くて、以下の実装で十分。

public class MyStudentList : StudentList, IEnumerable<Student>
{
    public MyStudentList() { }

    public MyStudentList(int studentCount): base(studentCount) { }

    public IEnumerator<Student> GetEnumerator()
    {
        var lastNumber = GetLastNum();
        for (var i = 0; i < lastNumber; i++)
        {
            yield return GetStudentAt(i);
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}