当类型参数在编译时不知道,而是在运行时动态获得时,调用泛型方法的最佳方法是什么?

考虑下面的示例代码-在Example()方法中,使用myType变量中存储的Type调用GenericMethod<T>()的最简洁的方法是什么?

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>()
    {
        //...
    }
}

当前回答

在阿德里安·加勒罗的回答中补充道:

从type info调用泛型方法涉及三个步骤。

##TLDR:使用类型对象调用已知的泛型方法可以通过:##完成

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

其中GenericMethod<object>是要调用的方法名和满足泛型约束的任何类型。

(Action)匹配要调用的方法的签名,即(Func<string,string,int> or Action<bool>)

第一步是获取泛型方法定义的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”实际上指的是一组可能包含一个或多个方法的方法。在您提供方法参数的类型之前,不知道是哪一种 方法。

((Action)GenericMethod<object>)指的是特定方法的委托。((Func <字符串,int >) GenericMethod <对象>) 引用GenericMethod的不同重载

方法3:创建一个包含方法调用表达式的lambda表达式,获取MethodInfo对象,然后获取genericmethoddefinition

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

这可以分解为

创建一个lambda表达式,其中主体是对所需方法的调用。

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);

其他回答

没有人提供“经典的反射”解决方案,所以这里有一个完整的代码示例:

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(类型keyType,类型valueType)

并且它创建并返回一个字典实例,其键和值的类型恰好是调用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

这是我基于Grax的回答的2美分,但是对于一个泛型方法需要两个参数。

假设你的方法在一个Helpers类中定义如下:

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

在我的例子中,U类型总是一个存储类型T对象的可观察集合。

因为我已经预定义了我的类型,我首先创建了“虚拟”对象,表示可观察集合(U)和存储在其中的对象(T),这将在调用Make时用于下面获得它们的类型

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

然后调用GetMethod找到你的Generic函数:

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

到目前为止,上面的调用与上面解释的几乎相同,但当您需要传递多个参数时,有一点不同。

你需要传递一个Type[]数组给MakeGenericMethod函数,它包含上面创建的“虚拟”对象的类型:

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包含2个类型类型的属性,我在运行时根据传递给构造函数的枚举值设置,并将为我提供相关类型,然后在MakeGenericMethod中使用。

再次感谢你强调这个@Bevan。

你需要使用反射来获取方法,然后用MakeGenericMethod提供类型参数来“构造”它:

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

对于静态方法,将null作为第一个参数传递给Invoke。这与泛型方法无关——这只是普通的反射。

如上所述,在使用动态的c# 4中,很多事情都变得更简单了——当然,前提是您可以使用类型推断。它在类型推断不可用的情况下不起作用,例如问题中的确切示例。

只是对原来答案的一个补充。虽然这是可行的:

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创建它。我不知道有没有更好的办法。

在阿德里安·加勒罗的回答中补充道:

从type info调用泛型方法涉及三个步骤。

##TLDR:使用类型对象调用已知的泛型方法可以通过:##完成

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

其中GenericMethod<object>是要调用的方法名和满足泛型约束的任何类型。

(Action)匹配要调用的方法的签名,即(Func<string,string,int> or Action<bool>)

第一步是获取泛型方法定义的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”实际上指的是一组可能包含一个或多个方法的方法。在您提供方法参数的类型之前,不知道是哪一种 方法。

((Action)GenericMethod<object>)指的是特定方法的委托。((Func <字符串,int >) GenericMethod <对象>) 引用GenericMethod的不同重载

方法3:创建一个包含方法调用表达式的lambda表达式,获取MethodInfo对象,然后获取genericmethoddefinition

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

这可以分解为

创建一个lambda表达式,其中主体是对所需方法的调用。

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);