遅延評価

LINQではC#2.0の新機能であるイテレータ(yield)が使われています。イテレータは発表初期にforeachが簡単に書けると言った紹介のされ方だったので、その真価が伝わっていない気がします。例えば、LINQでも使われている遅延評価とか。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Query;

// やっつけでこさえたIEnumerator/IEnumerable
// Currentにアクセスしたらコンソール出力させたかっただけ
class MyEnumerator<T> : IEnumerator<T>, IEnumerable<T> {
    T arr;
    int cur;

    public MyEnumerator (T arr) {
        this.arr = arr;
        Reset ();
    }

    public void Dispose () {}

    public IEnumerator<T> GetEnumerator () {
        return new MyEnumerator<T> (arr);
    }
    IEnumerator IEnumerable.GetEnumerator () {
        return GetEnumerator ();
    }

    public void Reset () {
        cur = -1;
    }

    public bool MoveNext () {
        if (cur >= arr.Length)
            return false;
        return ++cur < arr.Length;
    }

    public T Current {
        get { 
            Console.Write ("{0},", arr [cur]);
            return arr [cur];
        }
    }
    object IEnumerator.Current {
        get { 
            return Current;
        }
    }
}

static class Program {
    // Sequence.Whereとの比較用メソッド
    static IEnumerable<T> MyWhere<T> (IEnumerable<T> e, Predicate<T> pred) {
        List<T> list = new List<T> ();
        foreach (T t in e) 
            // predを満たす要素のみlistに格納する
            if (pred (t))
                list.Add (t);
        return list;
    }

    static void Main () {
        var nums = new [] { 1, 3, 5, 7, 9, 8, 6, 4, 2, 0 };
        var e = new MyEnumerator<int> (nums);

        Console.WriteLine ("# MyWhere");
        var nums1 = MyWhere<int> (e, x => x % 2 == 0);
        Console.WriteLine ();

        // Sequence.Whereは遅延評価される

        Console.WriteLine ("# Where");
        // num2は状態遷移マシンが返されるだけ
        // MyWhereと違ってコンソールには出力されない
        var num2 = Sequence.Where (e, x => x % 2 == 0);
        Console.WriteLine ();

        // ここで評価される
        Console.WriteLine ("# foreach");
        foreach (var n in num2)
            Console.WriteLine ("*{0}*", n);
    }
}
/* 結果
# MyWhere
1,3,5,7,9,8,6,4,2,0,
# Where

# foreach
1,3,5,7,9,8,*8*
6,*6*
4,*4*
2,*2*
0,*0*
 */

イテレーター無しでLINQ(というかSystem.Query)を実装したら、MyWhereみたいにメモリ無駄食いで使い物になりませんよね。(^^;