あんまり管理もしてない今日この頃ですが皆様いか(略)。
今回は型引数をバインドするカリー化ができないかと言う話。
一応解説しておくと、カリー化とは要するに引数の一部を固定した関数を作ることです。こんなサンプルコードでご理解いただけるでしょうか。
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 と同じ記述になりました。
しかし一々クラスを用意しなければならないってのはちょっと面倒ですね。