Ktouth Brand. on Web

け〜くんこと K.Ktouth のだらだらした日常と突発的に作るプログラムや読み物とかの雑多サイト



[2010年08月08日]

リフレクションとジェネリック

2010年08月09日 08:31更新 筆者:K.Ktouth

Effective C#(AA)

最近、単体テストを組むときにジェネリック周りのメソッドを呼び出す際にいろいろ困ったので、簡単に解決策をリストアップ。
(以下、簡単な解決策)

Type でジェネリック型を取得する

例えば List<string> の Type を取得するには typeof(List<string>) で良いわけですが、テストの段階など型パラメータの中身が決まっていないことも多いわけです。
以前は型パラメータが指定されていないオープンジェネリック型の取得に Type.GetType(string) を使っていたんですが……

Type typ = Type.GetType("System.Collections.Generic.List~1, mscorlib"); // 完全型修飾名で指定

この方法だと、どうもアセンブリや型によっては取得出来ないっぽいです。なぜか Func<TResult> 、つまり "System.Func~1, System.Core" が取得出来なくて苦労しました。
で、この解決方ですが……アホみたいな話でした。

Type typ = typeof(List<>); // typeof 構文の中では型パラメータ指定が無くてもOK

……素直に書くだけでしーた orz
通常、型パラメータ無しでの記述は即文法エラーなんで気づきもしねぇぇ……かなし

ジェネリックメソッドのリフレクション

ジェネリックメソッドであろうとシンプルに Type.GetMethod(...) で取得出来ます。が、定義が複雑なぶん、同名のメソッドでオーバーロードされている場合、うまく行きません。要はメソッド一覧から条件指定して抽出しろと言う話です。

細かいことはこちらのサイトに書かれてます。

MethodInfo.Invoke と ref/out 指定パラメータは相性が悪い

上記二つの方法を使えば MethodInfo の取得は簡単ですが、Invoke メソッドのパラメータに ref/out が指定されていて、そのメソッド内で例外が発生すると、ref/out 指定のパラメータへの変更が反映されないという問題があります。問題というか Invoke メソッドの仕様です。その発生した例外も TargetInvocationException 例外でラッピングされますしね。
この仕様を越えてパラメータの反映をさせたい場合、Invoke メソッドで直接呼び出すのではなく、いったんデリゲートに型変換してから実行することで解決出来ます。

// public void FooBarBaz<TEventArgs>(object sender, TEventArgs e, ref int counter);
// ……と言うメソッドの MethodInfo を取得しているとする

// 対応するデリゲートを定義しておく
private delegate void FooBarBazDelegate<TEventArgs>(object sender, TEventArgs e, ref int counter);

// (デリゲートの型、インスタンスメソッドを呼び出すオブジェクト、MethodInfo)
var dFunc = Delegate.CreateDelegate(typeof(FooBarBazDelegate<TEventArgs>),
                  _Source,
                  _Info.MakeGenericMethod(typeof(TEventArgs))) as FooBarBazDelegate<TEventArgs>;
dFunc(sender, e, ref counter);

こんな感じです。静的メソッドの場合は第2パラメータを省略します。

本日のリンク元
アンテナ
その他のリンク元
検索