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

考虑下面的示例代码-在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>()
    {
        //...
    }
}

当前回答

在c# 4.0中,反射是不必要的,因为DLR可以使用运行时类型调用它。由于动态地使用DLR库有点麻烦(而不是c#编译器为您生成代码),开源框架Dynamitey (.net standard 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}));

其他回答

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

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

在c# 4.0中,反射是不必要的,因为DLR可以使用运行时类型调用它。由于动态地使用DLR库有点麻烦(而不是c#编译器为您生成代码),开源框架Dynamitey (.net standard 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}));

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

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

这是我基于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。

受Enigmativity的答案启发-让我们假设你有两个(或更多)类,比如

public class Bar { }
public class Square { }

你想调用方法Foo<T> with Bar and Square,它被声明为

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

然后你可以实现一个Extension方法:

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

这对每个类都适用。在这种情况下,它将输出:

广场 酒吧