2008年07月05日

ジェネリックカレー

あんまり管理もしてない今日この頃ですが皆様いか(略)。

今回は型引数をバインドするカリー化ができないかと言う話。

一応解説しておくと、カリー化とは要するに引数の一部を固定した関数を作ることです。こんなサンプルコードでご理解いただけるでしょうか。

static int Add(int x, int y) { return x + y; }
static int Add5(int x) { return Add(x, 5); }
public static void Main() {
    Console.WriteLine(Add5(4));
}

この場合、Add メソッドの引数 y を 5 に固定した Add5 メソッドを新たに作成しています。まあサンプルのためのサンプルなんで意味は無いですけどね。ちなみに関数作成はラムダ式など使って表現することが多いです。

static void Main() {
    Func<int, int> addx = x => Add(x, 5);
    Console.WriteLine(addx(4));
}
' ちなみに VB だとラムダ式はこんな形になるそうな。
Public Shared Sub Main()
    Dim addx As Func(Of Integer, Integer) = Function(x) Add(x, 5)
    Console.WriteLine(addx(4))
End Sub

さて本題ですが、まず何をやりたかったのかから説明しましょう。

幾つかのアセンブリから、特定の型の派生クラスの一覧を調べようと思ったのがきっかけでした。具体的に言うと WPF の MarkupExtension の派生クラスを。

取りあえずベタに書きます。

public static void Main() {
    var types1 = typeof(Window).Assembly.GetTypes()
                   .Where(type => type.IsSubclassOf(typeof(MarkupExtension)));
    var types2 = typeof(Rect).Assembly.GetTypes()
                   .Where(type => type.IsSubclassOf(typeof(MarkupExtension)));
    var types3 = typeof(Duration).Assembly.GetTypes()
                   .Where(type => type.IsSubclassOf(typeof(MarkupExtension)));
    // 以下、types を Concat したり表示したり
}

露骨に記述が重複しているので、これはメソッドにするのが一般的でしょう。

public static IEnumerable<Type> GetMarkupExtensions(Type type) {
    return type.Assembly.GetTypes()
                   .Where(t => t.IsSubclassOf(typeof(MarkupExtension)));
}
public static void Main() {
    var types1 = GetMarkupExtensions(typeof(Window));
}

引数が Type 型なら、一々呼び出し元で typeof するより、型引数で表現して GetMarkupExtensions メソッド内で typeof した方がすっきりする気がします。

public static IEnumerable<Type> GetMarkupExtensions<T>() {
    return GetMarkupExtensions(typeof(T));
}
public static void Main() {
    var types1 = GetMarkupExtensions<Window>();
}

ここでこの GetMarkupExtensions を汎用化することを考えます。つまり、任意のアセンブリ(に属する型)から任意の型の派生クラスを列挙するメソッドです。

public static IEnumerable<Type> GetSubclasses<TAnyInAssembly, TBase>() {
    return typeof(TAnyInAssembly).Assembly.GetTypes()
                   .Where(t => t.IsSubclassOf(typeof(TBase)));
}
public static void Main() {
    var types1 = GetSubclasses<Window, MarkupExtension>();
    var types2 = GetSubclasses<Rect, MarkupExtension>();
    var types3 = GetSubclasses<Duration, MarkupExtension>();
}

汎用化した結果、逆に Main の方で記述が増えてしまいました。どうせ型引数のうち MarkupExtension は同じなんですからこれを固定して型引数一つで済ませたい。こんな感じです。

public static void Main() {
    var func = BindToGetSubclasses<MarkupExtension>();
    var types1 = func<Window>();
    var types2 = func<Rect>();
    var types3 = func<Duration>();
}

BindToGetSubclasses は Func<IEnumerable<Type>> 相当のデリゲートインスタンスを返すことになりますが、ここで困ったことになります。デリゲートインスタンスは作成時点で既に型引数は解決済みでなければならないのです。インスタンスですから当然ですが。

てなわけでメソッドでは解決できないようです。ここはもう一つ大きい区切り、つまりクラスに目をやります。クラスにも型引数を使用できます。ならばこちらでバインドしてやればいいのではないか。

public class SubclassEnumerator<TBase> {
    public IEnumerable<Type> GetSubclasses<TAnyInAssembly>() {
        return typeof(TAnyInAssembly).Assembly.GetTypes()
                   .Where(t => t.IsSubclassOf(typeof(TBase)));
    }
}
public static void Main() {
    var e = new SubclassEnumerator<MarkupExtension>();
    var types1 = e.GetSubclasses<Window>();
    var types2 = e.GetSubclasses<Rect>();
    var types3 = e.GetSubclasses<Duration>();
}

これでほぼ BindToGetSubclasses と同じ記述になりました。

しかし一々クラスを用意しなければならないってのはちょっと面倒ですね。

posted by Hongliang at 13:01| Comment(0) | TrackBack(0) | C# | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
この記事へのトラックバックURL
http://blog.seesaa.jp/tb/102200459

この記事へのトラックバック

ここ(hongliang.seesaa.net)で公開しているものについて、利用は自由に行って頂いて構いません。改変、再頒布もお好きになさって下さい。利用に対しこちらが何かを要求することはありません。

ただし、公開するものを使用、または参考したことによって何らかの損害等が生じた場合でも、私はいかなる責任も負いません。

あ、こんなのに使ったってコメントを頂ければ嬉しいです。

×

この広告は1年以上新しい記事の投稿がないブログに表示されております。