リフレクションを使用してジェネリックメソッドを呼び出すにはどうすればよいですか?


1071

型パラメーターがコンパイル時に不明であるが、実行時に動的に取得される場合にジェネリックメソッドを呼び出す最善の方法は何ですか?

次のサンプルコードについて考えてみましょう。Example()メソッド内で、変数に格納されたものをGenericMethod<T>()使用して呼び出す最も簡潔な方法は何ですか。TypemyType

public class Sample
{
    public void Example(string typeName)
    {
        Type myType = FindType(typeName);

        // What goes here to call GenericMethod<T>()?
        GenericMethod<myType>(); // This doesn't work

        // What changes to call StaticMethod<T>()?
        Sample.StaticMethod<myType>(); // This also doesn't work
    }

    public void GenericMethod<T>()
    {
        // ...
    }

    public static void StaticMethod<T>()
    {
        //...
    }
}

7
私はJonのソリューションを試してみましたが、クラスでジェネリックメソッドをパブリックにするまで機能しませんでした。別のJonがbindingflagsを指定する必要があると答えたのはわかっていますが、これは役に立ちませんでした。
naskew 2012年

12
また、private / internalメソッドを取得するには、BindingFlags.Instanceだけでなくも必要BindingFlags.NonPublicです。
Lars Kemmann、2013

2
この質問の最新バージョン:stackoverflow.com/q/2433436/103167
Ben Voigt

@ピーター・モーテンセン-「私は「?」の前にスペースを使用しました 英語のパーツを英語以外の(C#)パーツから分離する。私見スペースを削除すると、次のようになりますか?コードの一部です。コードがない場合は、スペースを削除することに同意しますが、この場合は...
Bevan

回答:


1138

リフレクションを使用してメソッドを開始し、MakeGenericMethodで型引数を指定して「構築」する必要があります。

MethodInfo method = typeof(Sample).GetMethod(nameof(Sample.GenericMethod));
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);

静的メソッドの場合null、最初の引数としてに渡しますInvoke。これはジェネリックメソッドとは関係ありません。通常のリフレクションです。

述べたように、これの多くはC#4を使用したdynamic場合よりも簡単です。もちろん、型推論を使用できる場合は。質問の正確な例のように、型推論が利用できない場合には役に立ちません。


92
+1; GetMethod()デフォルトではパブリックインスタンスメソッドのみが考慮されるため、BindingFlags.Staticおよび/またはが必要になる場合があることに注意してくださいBindingFlags.NonPublic

20
フラグの正しい組み合わせはBindingFlags.NonPublic | BindingFlags.Instance(およびオプションでBindingFlags.Static)です。
Lars Kemmann、2013

4
これの重複をマークする質問は、静的メソッドでこれを行う方法を不思議に思っています-そして技術的にはここで質問をします。静的メソッドを呼び出すときは、generic.Invoke()の最初のパラメーターをnullにする必要があります。最初のパラメーターは、インスタンスメソッドを呼び出すときにのみ必要です。
Chris Moschini 2013年

2
@ChrisMoschini:回答にそれを追加しました。
Jon Skeet

2
@gzou:答えに何かを追加しました-ただし、質問のジェネリックメソッドを呼び出す場合dynamicは、型推論が利用できないため役に立たないことに注意してください。(コンパイラーがタイプ引数を決定するために使用できる引数はありません。)
Jon Skeet

170

元の答えに追加しただけです。これは機能しますが、

MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);

また、のコンパイル時のチェックが失われるという点でも少し危険ですGenericMethod。後でリファクタリングと名前の変更を行うとGenericMethod、このコードは気付かず、実行時に失敗します。また、アセンブリの後処理(たとえば、未使用のメソッド/クラスの難読化または削除)がある場合、このコードも壊れる可能性があります。

したがって、コンパイル時にリンクするメソッドがわかっていて、これが何百万回も呼び出されないため、オーバーヘッドが問題にならない場合は、このコードを次のように変更します。

Action<> GenMethod = GenericMethod<int>;  //change int by any base type 
                                          //accepted by GenericMethod
MethodInfo method = this.GetType().GetMethod(GenMethod.Method.Name);
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);

あまりきれいではありませんが、GenericMethodここにコンパイル時の参照があります。リファクタリング、削除、またはで何かを実行するとGenericMethod、このコードは機能し続けるか、少なくともコンパイル時に中断します(たとえばを削除した場合GenericMethod)。

同じことを行う別の方法は、新しいラッパークラスを作成し、それを使用して作成することActivatorです。もっと良い方法があるかどうかわかりません。


5
リフレクションを使用してメソッドを呼び出す場合、通常、メソッド名自体が別のメソッドによって発見されます。メソッド名を事前に知ることは一般的ではありません。
Bevan

13
まあ、私は反射の一般的な使用に同意します。しかし、元の質問は「GenericMethod <myType>()」を呼び出す方法でした。その構文が許可されている場合、GetMethod()はまったく必要ありません。しかし、「GenericMethod <myType>をどのように書くのですか?」という質問の場合、答えにはGenericMethodとのコンパイル時のリンクが失われないようにする方法が含まれていると思います。この質問が一般的であるかどうかはわかりませんが、私は昨日この正確な問題があったことを知っています、そしてそれが私がこの質問に着いた理由です
Adrian Gallero '28

20
GenMethod.Method.GetGenericMethodDefinition()代わりにできますthis.GetType().GetMethod(GenMethod.Method.Name)。少しすっきりとしており、おそらく安全です。
Daniel Cassidy、

サンプルの「myType」はどういう意味ですか?
開発者

37
今すぐ使用できますnameof(GenericMethod)
dmigo 2016年

140

dynamicリフレクションAPIの代わりに型を使用することで、実行時にのみ既知の型パラメーターを使用してジェネリックメソッドを呼び出すことが大幅に簡略化されます。

この手法を使用するには、Typeクラスのインスタンスだけでなく、実際のオブジェクトから型を知る必要があります。それ以外の場合は、そのタイプのオブジェクトを作成するか、標準のリフレクションAPI ソリューションを使用する必要があります。Activator.CreateInstanceメソッドを使用してオブジェクトを作成できます。

ジェネリックメソッドを呼び出したい場合、「通常の」使用では、その型が推論されていたはずですが、不明な型のオブジェクトをにキャストするだけdynamicです。次に例を示します。

class Alpha { }
class Beta { }
class Service
{
    public void Process<T>(T item)
    {
        Console.WriteLine("item.GetType(): " + item.GetType()
                          + "\ttypeof(T): " + typeof(T));
    }
}

class Program
{
    static void Main(string[] args)
    {
        var a = new Alpha();
        var b = new Beta();

        var service = new Service();
        service.Process(a); // Same as "service.Process<Alpha>(a)"
        service.Process(b); // Same as "service.Process<Beta>(b)"

        var objects = new object[] { a, b };
        foreach (var o in objects)
        {
            service.Process(o); // Same as "service.Process<object>(o)"
        }
        foreach (var o in objects)
        {
            dynamic dynObj = o;
            service.Process(dynObj); // Or write "service.Process((dynamic)o)"
        }
    }
}

そして、これがこのプログラムの出力です:

item.GetType(): Alpha    typeof(T): Alpha
item.GetType(): Beta     typeof(T): Beta
item.GetType(): Alpha    typeof(T): System.Object
item.GetType(): Beta     typeof(T): System.Object
item.GetType(): Alpha    typeof(T): Alpha
item.GetType(): Beta     typeof(T): Beta

ProcessGetType()メソッドを使用して)渡された引数の実数型と(typeof演算子を使用して)汎用パラメーターの型を書き込む汎用インスタンスメソッドです。

オブジェクト引数をdynamic型にキャストすることにより、実行時まで型パラメーターの提供を延期しました。引数を指定Processしてメソッドが呼び出されると、dynamicコンパイラーはこの引数のタイプを気にしません。コンパイラーは、実行時に渡された引数の実際のタイプを(リフレクションを使用して)チェックし、呼び出すのに最適なメソッドを選択するコードを生成します。ここには、この1つのジェネリックメソッドしかないため、適切な型パラメーターで呼び出されます。

この例では、出力は次のように記述した場合と同じです。

foreach (var o in objects)
{
    MethodInfo method = typeof(Service).GetMethod("Process");
    MethodInfo generic = method.MakeGenericMethod(o.GetType());
    generic.Invoke(service, new object[] { o });
}

動的タイプのバージョンは間違いなく短く、書くのが簡単です。また、この関数を複数回呼び出すパフォーマンスについても心配する必要はありません。同じタイプの引数を使用した次の呼び出しは、DLRのキャッシングメカニズムのおかげで高速になるはずです。もちろん、呼び出されたデリゲートをキャッシュするコードを記述できますが、dynamic型を使用することで、この動作を無料で取得できます。

呼び出すジェネリックメソッドにパラメーター化された型の引数がない場合(その型パラメーターを推測できないため)、ジェネリックメソッドの呼び出しを次の例のようにヘルパーメソッドでラップできます。

class Program
{
    static void Main(string[] args)
    {
        object obj = new Alpha();

        Helper((dynamic)obj);
    }

    public static void Helper<T>(T obj)
    {
        GenericMethod<T>();
    }

    public static void GenericMethod<T>()
    {
        Console.WriteLine("GenericMethod<" + typeof(T) + ">");
    }
}

型安全性の向上

dynamicリフレクションAPIを使用する代わりにオブジェクトを使用することの本当に優れている点は、実行時までわからないこの特定の型のコンパイル時間チェックだけが失われることです。その他の引数とメソッドの名前は、通常どおりコンパイラーによって静的に分析されます。引数を削除または追加したり、型を変更したり、メソッド名を変更したりすると、コンパイル時エラーが発生します。これは、メソッド名を文字列Type.GetMethodとして、引数をオブジェクト配列として指定した場合は発生しませんMethodInfo.Invoke

以下は、コンパイル時(コメントコード)と実行時のその他のエラーを捕捉する方法を示す簡単な例です。また、DLRがどのメソッドを呼び出すかを解決しようとする方法も示しています。

interface IItem { }
class FooItem : IItem { }
class BarItem : IItem { }
class Alpha { }

class Program
{
    static void Main(string[] args)
    {
        var objects = new object[] { new FooItem(), new BarItem(), new Alpha() };
        for (int i = 0; i < objects.Length; i++)
        {
            ProcessItem((dynamic)objects[i], "test" + i, i);

            //ProcesItm((dynamic)objects[i], "test" + i, i);
            //compiler error: The name 'ProcesItm' does not
            //exist in the current context

            //ProcessItem((dynamic)objects[i], "test" + i);
            //error: No overload for method 'ProcessItem' takes 2 arguments
        }
    }

    static string ProcessItem<T>(T item, string text, int number)
        where T : IItem
    {
        Console.WriteLine("Generic ProcessItem<{0}>, text {1}, number:{2}",
                          typeof(T), text, number);
        return "OK";
    }
    static void ProcessItem(BarItem item, string text, int number)
    {
        Console.WriteLine("ProcessItem with Bar, " + text + ", " + number);
    }
}

ここで、引数をdynamic型にキャストすることで、再度いくつかのメソッドを実行します。最初の引数の型の検証のみがランタイムに延期されます。呼び出すメソッドの名前が存在しない場合、または他の引数が無効な場合(引数の数が間違っているか、型が間違っている場合)、コンパイラエラーが発生します。

dynamic引数をメソッドに渡すと、この呼び出しは最近バインドされます。メソッドのオーバーロードの解決は実行時に行われ、最適なオーバーロードを選択しようとします。そのため、型のProcessItemオブジェクトを使用してメソッドを呼び出すと、BarItem実際には非ジェネリックメソッドが呼び出されます。これは、この型に適しているためです。ただし、Alphaこのオブジェクトを処理できるメソッドがないため、型の引数を渡すとランタイムエラーが発生します(ジェネリックメソッドには制約がwhere T : IItemあり、Alphaクラスにはこのインターフェイスが実装されていません)。しかし、それがすべてのポイントです。コンパイラには、この呼び出しが有効であるという情報がありません。あなたはプログラマーとしてこれを知っており、このコードがエラーなしで実行されることを確認する必要があります。

戻り型の問題

あなたは、ダイナミック型のパラメータを持つ非空メソッドを呼び出している場合は、その戻り値の型は、おそらくだろうことがdynamic、あまりにも。したがって、前の例をこのコードに変更する場合:

var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);

その場合、結果オブジェクトのタイプはになりますdynamic。これは、コンパイラが呼び出されるメソッドを常に認識しているわけではないためです。関数呼び出しの戻り値の型がわかっている場合は、それを必要な型に暗黙的に変換して、残りのコードが静的に型付けされるようにする必要があります。

string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);

タイプが一致しない場合、ランタイムエラーが発生します。

実際、前の例で結果値を取得しようとすると、2番目のループ反復でランタイムエラーが発生します。これは、void関数の戻り値を保存しようとしたためです。


Mariusz、「このオブジェクトを処理できるメソッドがないため、Alpha型の引数を渡すとランタイムエラーが発生します。」var a = new Alpha()ProcessItem(a、 "test" + iを呼び出すと、i)汎用的なProcessItemメソッドがこれを効果的に処理して「一般的な処理アイテム」を出力しないのはなぜですか?
Alex Edelstein

@AlexEdelstein私は少し明確にするために私の回答を編集しました。これは、ジェネリックProcessItemメソッドにはジェネリック制約があり、IItemインターフェイスを実装するオブジェクトのみを受け入れるためです。いつ電話するかProcessItem(new Aplha(), "test" , 1);ProcessItem((object)(new Aplha()), "test" , 1);、コンパイラエラーが発生するが、キャストするdynamicと、そのチェックがランタイムに延期されます。
Mariusz Pawelski 2015年

素晴らしい答えと説明で、私にはぴったりです。受け入れられた回答よりもはるかに優れており、書く時間が短く、パフォーマンスが高く、安全です。
ygoe 2015

17

C#4.0では、DLRがランタイムタイプを使用してリフレクションを呼び出すことができるため、リフレクションは必要ありません。DLRライブラリーを使用すると、動的に(C#コンパイラーがコードを生成する代わりに)面倒な作業になるため、オープンソースフレームワークDynamitey(.net標準1.5)を使用すると、コンパイラーが生成するのと同じ呼び出しにキャッシュされたランタイムアクセスが簡単になりますあなたのために。

var name = InvokeMemberName.Create;
Dynamic.InvokeMemberAction(this, name("GenericMethod", new[]{myType}));


var staticContext = InvokeContext.CreateStatic;
Dynamic.InvokeMemberAction(staticContext(typeof(Sample)), name("StaticMethod", new[]{myType}));

13

に追加 エイドリアンガレロの答えに

タイプ情報からジェネリックメソッドを呼び出すには、3つのステップが必要です。

TLDR:型オブジェクトを使用して既知のジェネリックメソッドを呼び出すには、次の方法があります。

((Action)GenericMethod<object>)
    .Method
    .GetGenericMethodDefinition()
    .MakeGenericMethod(typeof(string))
    .Invoke(this, null);

どこ GenericMethod<object>は呼び出すメソッド名と一般的な制約を満たす任意の型です。

(アクション)呼び出されるメソッドのシグネチャに一致します(つまり(Func<string,string,int>またはAction<bool>))

ステップ1は、ジェネリックメソッド定義のMethodInfoを取得しています

方法1:適切な型またはバインディングフラグを指定してGetMethod()またはGetMethods()を使用します。

MethodInfo method = typeof(Sample).GetMethod("GenericMethod");

方法2:デリゲートを作成し、MethodInfoオブジェクトを取得して、GetGenericMethodDefinitionを呼び出します

メソッドを含むクラス内から:

MethodInfo method = ((Action)GenericMethod<object>)
    .Method
    .GetGenericMethodDefinition();

MethodInfo method = ((Action)StaticMethod<object>)
    .Method
    .GetGenericMethodDefinition();

メソッドを含むクラスの外側から:

MethodInfo method = ((Action)(new Sample())
    .GenericMethod<object>)
    .Method
    .GetGenericMethodDefinition();

MethodInfo method = ((Action)Sample.StaticMethod<object>)
    .Method
    .GetGenericMethodDefinition();

C#では、メソッドの名前、つまり「ToString」または「GenericMethod」は、実際には1つ以上のメソッドを含む可能性があるメソッドのグループを指します。メソッドパラメータのタイプを指定するまで、どのメソッドを参照しているかはわかりません。

((Action)GenericMethod<object>) 特定のメソッドのデリゲートを参照します。 ((Func<string, int>)GenericMethod<object>) GenericMethodの別のオーバーロードを参照します

方法3:メソッド呼び出し式を含むラムダ式を作成し、MethodInfoオブジェクトを取得してからGetGenericMethodDefinition

MethodInfo method = ((MethodCallExpression)((Expression<Action<Sample>>)(
    (Sample v) => v.GenericMethod<object>()
    )).Body).Method.GetGenericMethodDefinition();

これは分解します

本体が目的のメソッドの呼び出しであるラムダ式を作成します。

Expression<Action<Sample>> expr = (Sample v) => v.GenericMethod<object>();

本文を抽出し、MethodCallExpressionにキャストします

MethodCallExpression methodCallExpr = (MethodCallExpression)expr.Body;

メソッドからジェネリックメソッド定義を取得する

MethodInfo methodA = methodCallExpr.Method.GetGenericMethodDefinition();

ステップ2はMakeGenericMethodを呼び出して、適切なタイプのジェネリックメソッドを作成します。

MethodInfo generic = method.MakeGenericMethod(myType);

ステップ3は、適切な引数を指定してメソッドを呼び出すことです。

generic.Invoke(this, null);

8

誰も「クラシックリフレクション」ソリューションを提供しなかったので、ここに完全なコード例を示します。

using System;
using System.Collections;
using System.Collections.Generic;

namespace DictionaryRuntime
{
    public class DynamicDictionaryFactory
    {
        /// <summary>
        /// Factory to create dynamically a generic Dictionary.
        /// </summary>
        public IDictionary CreateDynamicGenericInstance(Type keyType, Type valueType)
        {
            //Creating the Dictionary.
            Type typeDict = typeof(Dictionary<,>);

            //Creating KeyValue Type for Dictionary.
            Type[] typeArgs = { keyType, valueType };

            //Passing the Type and create Dictionary Type.
            Type genericType = typeDict.MakeGenericType(typeArgs);

            //Creating Instance for Dictionary<K,T>.
            IDictionary d = Activator.CreateInstance(genericType) as IDictionary;

            return d;

        }
    }
}

上記のDynamicDictionaryFactoryクラスにはメソッドがあります

CreateDynamicGenericInstance(Type keyType, Type valueType)

また、IDictionaryインスタンスを作成して返します。そのキーと値のタイプは、呼び出しkeyTypeとで指定されたとおりvalueTypeです。

このメソッドを呼び出してインスタンス化して使用する方法の完全な例を次に示しますDictionary<String, int>

using System;
using System.Collections.Generic;

namespace DynamicDictionary
{
    class Test
    {
        static void Main(string[] args)
        {
            var factory = new DictionaryRuntime.DynamicDictionaryFactory();
            var dict = factory.CreateDynamicGenericInstance(typeof(String), typeof(int));

            var typedDict = dict as Dictionary<String, int>;

            if (typedDict != null)
            {
                Console.WriteLine("Dictionary<String, int>");

                typedDict.Add("One", 1);
                typedDict.Add("Two", 2);
                typedDict.Add("Three", 3);

                foreach(var kvp in typedDict)
                {
                    Console.WriteLine("\"" + kvp.Key + "\": " + kvp.Value);
                }
            }
            else
                Console.WriteLine("null");
        }
    }
}

上記のコンソールアプリケーションを実行すると、期待どおりの正しい結果が得られます。

Dictionary<String, int>
"One": 1
"Two": 2
"Three": 3

2

これはGraxの回答に基づく私の2セントですが、ジェネリックメソッドには2つのパラメータが必要です。

メソッドがHelpersクラスで次のように定義されているとします。

public class Helpers
{
    public static U ConvertCsvDataToCollection<U, T>(string csvData)
    where U : ObservableCollection<T>
    {
      //transform code here
    }
}

私の場合、Uタイプは常にタイプTのオブジェクトを格納する監視可能なコレクションです。

タイプを事前定義しているので、最初に、監視可能なコレクション(U)とそこに格納されているオブジェクト(T)を表す「ダミー」オブジェクトを作成します。

object myCollection = Activator.CreateInstance(collectionType);
object myoObject = Activator.CreateInstance(objectType);

次に、GetMethodを呼び出して、ジェネリック関数を見つけます。

MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");

これまでのところ、上記の呼び出しは上記で説明したものとほとんど同じですが、複数のパラメーターを渡す必要がある場合は少し異なります。

上記で作成した「ダミー」オブジェクトのタイプを含むMakeGenericMethod関数にType []配列を渡す必要があります。

MethodInfo generic = method.MakeGenericMethod(
new Type[] {
   myCollection.GetType(),
   myObject.GetType()
});

それが完了したら、上記のようにInvokeメソッドを呼び出す必要があります。

generic.Invoke(null, new object[] { csvData });

これで完了です。魅力的な作品!

更新:

@Bevanが強調したように、パラメーターを受け取るため、MakeGenericMethod関数を呼び出すときに配列を作成する必要はありません。また、この関数に直接型を渡すだけなので、型を取得するためにオブジェクトを作成する必要はありません。私の場合、別のクラスで事前定義された型があるので、コードを次のように変更しました。

object myCollection = null;

MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");

MethodInfo generic = method.MakeGenericMethod(
   myClassInfo.CollectionType,
   myClassInfo.ObjectType
);

myCollection = generic.Invoke(null, new object[] { csvData });

myClassInfoにはType、コンストラクターに渡された列挙値に基づいて実行時に設定したタイプの2つのプロパティが含まれており、MakeGenericMethodで使用する関連タイプを提供します。

この@Bevanをハイライトしてくれてありがとう。


引数MakeGenericMethod()持ちのparamsアレイを作成する必要はありませんので、キーワードを。タイプを取得するためにインスタンスを作成する必要もありませんmethodInfo.MakeGenericMethod(typeof(TCollection), typeof(TObject))。それで十分です。
Bevan

0

Enigmativityの答えに触発された-あなたが2つ(またはそれ以上)のクラスを持っているとしましょう

public class Bar { }
public class Square { }

と宣言されFoo<T>ているBarand Squareでメソッドを呼び出したい

public class myClass
{
    public void Foo<T>(T item)
    {
        Console.WriteLine(typeof(T).Name);
    }
}

次に、次のような拡張メソッドを実装できます。

public static class Extension
{
    public static void InvokeFoo<T>(this T t)
    {
        var fooMethod = typeof(myClass).GetMethod("Foo");
        var tType = typeof(T);
        var fooTMethod = fooMethod.MakeGenericMethod(new[] { tType });
        fooTMethod.Invoke(new myClass(), new object[] { t });
    }
}

これにより、次のFooように呼び出すことができます。

var objSquare = new Square();
objSquare.InvokeFoo();

var objBar = new Bar();
objBar.InvokeFoo();

どのクラスでも機能します。この場合、それは出力します:

スクエア
バー

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.