[c#] リフレクション付き右ジェネリックメソッドを選択



Answers

実行時の検索に文字列を渡すことなく、コンパイル時にメソッドの特定の汎用オーバーロードを幾分エレガントに選択することができます。

静的メソッド

次のような同じ名前の静的メソッドが複数あるとします。

public static void DoSomething<TModel>(TModel model)

public static void DoSomething<TViewModel, TModel>(TViewModel viewModel, TModel model)

// etc

探しているオーバーロードの総数とパラメータ数に一致するアクションまたはFuncを作成した場合、比較的少数のアクロバットでコンパイル時に選択できます。

例:最初のメソッドを選択する - voidを返すので、アクションを使用し、1つのジェネリックを取る。 オブジェクトを指定することで、型の指定を避けることができます。

var method = new Action<object>(MyClass.DoSomething<object>);

例:2番目のメソッドを選択します。voidを返します。したがって、2つのジェネリックタイプは2つのジェネリックパラメータのそれぞれに対してタイプオブジェクトを2回使用します。

var method = new Action<object, object>(MyClass.DoSomething<object, object>);

奇妙な配管をせずに、ランタイム検索や危険な文字列の使用をせずに、あなたが望む方法を得ただけです。

MethodInfo

通常、Reflectionでは、MethodInfoオブジェクトが必要です。このオブジェクトは、コンパイルセーフな方法で取得することもできます。 これは、メソッドで使用する実際のジェネリック型を渡すときです。 上記の2番目の方法が必要だったと仮定します。

var methodInfo = method.Method.MakeGenericMethod(type1, type2);

リフレクションの検索やGetMethod()への呼び出しや、薄い文字列のない汎用メソッドがあります。

静的拡張メソッド

オーバーロードされたQueryable.Withで引用した具体的な例では、Funcの定義でやや魅力的ですが、一般的に同じパターンに従います。 最も一般的に使用されるWhere()拡張メソッドのシグネチャは次のとおりです。

public static IQueryable<TModel> Where<TModel>(this IQueryable<TModel>, Expression<Func<TModel, bool>>)

明らかにこれはやや複雑になります - ここにあります:

var method = new Func<IQueryable<object>,
                      Expression<Func<object, bool>>,
                      IQueryable<object>>(Queryable.Where<object>);

var methodInfo = method.Method.MakeGenericMethod(modelType);

インスタンスメソッド

Valerieのコメントを組み込む - インスタンスメソッドを取得するには、非常に似たようなことをする必要があります。 あなたのクラスにこのインスタンスメソッドがあったとします:

public void MyMethod<T1>(T1 thing)

まず、静的と同じ方法でメソッドを選択します。

var method = new Action<object>(MyMethod<object>);

次に、 GetGenericMethodDefinition()を呼び出して一般的なMethodInfoにGetGenericMethodDefinition() 、最後にMakeGenericMethod()で型を渡します。

var methodInfo = method.Method.GetGenericMethodDefinition().MakeGenericMethod(type1);

Decoupling MethodInfoとパラメータ型

これは質問では要求されていませんが、一度上記を行うと、ある場所でメソッドを選択し、別の場所でメソッドを渡すタイプを決定する可能性があります。 これらの2つのステップを切り離すことができます。

渡す予定のジェネリック型パラメータが不明な場合は、それを取らずにMethodInfoオブジェクトを取得できます。

静的:

var methodInfo = method.Method;

インスタンス:

var methodInfo = method.Method.GetGenericMethodDefinition();

それを、メソッドをインスタンス化して呼び出すタイプを知っている他のメソッドに渡します。たとえば、次のようにします。

processCollection(methodInfo, type2);

...

protected void processCollection(MethodInfo method, Type type2)
{
    var type1 = typeof(MyDataClass);
    object output = method.MakeGenericMethod(type1, type2).Invoke(null, new object[] { collection });
}

これが特に役立つことの1つは、クラスの中からクラスの特定のインスタンスメソッドを選択し、後でそれを必要とする外部の呼び出し側にそれを公開することです。

編集:説明をクリーンアップし、Valerieのインスタンスメソッドの例を組み込みました。

Question

私はリフレクションを介して適切なジェネリックメソッドを選択し、それを呼び出したいと思います。

通常これはかなり簡単です。 例えば

var method = typeof(MyType).GetMethod("TheMethod");
var typedMethod = method.MakeGenericMethod(theTypeToInstantiate);

しかし、メソッドの一般的なオーバーロードが異なるときに問題が発生します。 たとえば、System.Linq.Queryableクラスのstaticメソッドです。 Where'-methodには2つの定義があります

static IQueryable<T> Where(this IQueryable<T> source, Expression<Func<T,bool>> predicate)
static IQueryable<T> Where(this IQueryable<T> source, Expression<Func<T,int,bool>> predicate)

これは、GetMethodが動作しないmeandです。なぜなら、2つをデスティニュイションできないからです。 したがって、私は正しいものを選択したいと思います。

これまでのところ、私はしばしば、必要に応じて第1または第2の方法をとった。 このような:

var method = typeof (Queryable).GetMethods().First(m => m.Name == "Where");
var typedMethod = method.MakeGenericMethod(theTypeToInstantiate);

しかし、私はこれに満足していません。なぜなら、私は最初の方法が正しいことを大きな前提にしているからです。 むしろ引数型で正しいメソッドを探したいと思っています。 しかし、私はどのように把握できませんでした。

私は '型'を渡して試しましたが、うまくいきませんでした。

        var method = typeof (Queryable).GetMethod(
            "Where", BindingFlags.Static,
            null,
            new Type[] {typeof (IQueryable<T>), typeof (Expression<Func<T, bool>>)},
            null);

どのように私は反射を介して '正しい'ジェネリックメソッドを見つけることができるという考えを誰もが持っています。 たとえば、クエリ可能クラスの 'Where'-methodの正しいバージョンですか?







Antamirの答えは私にとって非常に便利でしたが、見つかったメソッドのパラメータの数が、ジェネリック型とコンクリート型を混在して渡された型の数と一致することを検証しないというバグがあります。

たとえば、次のコマンドを実行したとします。

type.GetMethod("MyMethod",typeof(Refl.T1),typeof(bool))

2つの方法を区別することはできません。

MyMethod<T>(T arg1)
MyMethod<T>(T arg1, bool arg2)

2つの呼び出し:

var p = method.GetParameters();   

次のように変更する必要があります。

var p = method.GetParameters();   
if (p.Length != parameters.Length)
{
    correct = false;
    continue;
}

また、既存の 'break'行は両方とも 'continue'にする必要があります。




@ MBorosの答えに追加で。

このヘルパーメソッドを使用して、複雑な汎用引数を書くことを避けることができます:

public static MethodInfo GetMethodByExpression<Tin, Tout>(Expression<Func<IQueryable<Tin>, IQueryable<Tout>>> expr)  
{  
    return (expr.Body as MethodCallExpression).Method;  
} 

使用法:

var where = GetMethodByExpression<int, int>(q => q.Where((x, idx) => x > 2));  

または

var select = GetMethodByExpression<Person, string>(q => q.Select(x => x.Name));  



コンパイラがあなたにそれをさせる:

var fakeExp = (Expression<Func<IQueryable<int>, IQueryable<int>>>)(q => q.Where((x, idx) => x> 2));
var mi = ((MethodCallExpression)fakeExp.Body).Method.GetGenericMethodDefinition();

Where withインデックスを使用するか、2番目のパラメータをWhere式の中で除外します。




Links