読者です 読者をやめる 読者になる 読者になる

オブジェクト初期化子と名前付き省略可能引数: top down vs. bottom up (2.5)

.NET

id:siokoshou:20080130:p2 が面白かったので番外編.

IEnumerator<T> 連鎖の動きを spy する.

IEnumerator<T> を実装していれば必ず IDisposable である理由 - NyaRuRuの日記』で見たように,IEnumerator<T> はステートマシンで説明できます.さしずめ IEnumerator<T> 連鎖は連結ステートマシンということになりますが,この連鎖にモニタリング専用のステートマシンをはさんでみよう,という話です.実際これは拙作 Achiral に既に組み込まれています.

実装としては,ちょうど間に挟まるような何もしない IEnumerator<T> を作って,上流にイベントをフォワードする前後で登録されたデリゲートをコールバックすれば良さそうです.で,問題はこのコールバックの数が 10 ぐらいあるというところで,さてどうやって登録したものかという話です.

オブジェクト初期化子を名前付き省略可能引数の代わりに使う

現実には 10 個全てのイベントをモニタリングしたいということはそうそうありません.となると,フック IEnumerable<T> を作成するメソッド引数をどうするかという問題があります.VisualBasic であれば,省略可能引数を名前付きで呼び出せば 済みそうですが,C# でその手は使えません.
Achiral ではどういう実装にしたかというと,ここで「オブジェクト初期化子」を使いました.

using System;
using System.Linq;

using Achiral;
using Achiral.Extension;

static class Program
{
    static void Main(string[] args)
    {
        var source = Make.Repeat(new Random())
                         .Select(rand => rand.Next(0, 100))
                         .Hook(new EnumeratorHook<int>()
                               {
                                   OnBeforeBegin = () => Console.WriteLine("Beginning!"),
                                   OnAfterDispose = _ => Console.WriteLine("Disposed!"),
                               })
                         .Take(10);

        new[]
        { 
            source.Sum(),
            source.Count(),
            source.Max(),
            source.Min()
        }.ConsoleWriteLine();
    }
}

コールバックの登録は EnumeratorHook<T> に行い,設定されなかったものはデフォルトコンストラクタで null に指定され,モニタリングでは無視されるという扱いです.
実行してみます.

Beginning!
Disposed!
Beginning!
Disposed!
Beginning!
Disposed!
Beginning!
Disposed!
553
10
84
36

とまあこんな感じで,パイプラインの構築と解体が 4 回実行されたのが分かります.
弱点としては generic class のコンストラクタ呼び出しで型推論が使えないところで,上の例だと EnumeratorHook<int> の int が匿名型だった場合に書きようがなかったりもするのですが.
C# 4.0 では,コンストラクタ呼び出しで generic 型引数を推論させる仕組みが何か欲しいような気もしますね.