我如何使用reflection来调用一个通用的方法?

当编译时不知道types参数,而是在运行时dynamic地获得types参数时,调用generics方法的最好方法是什么?

考虑下面的示例代码 – 在Example()方法中,使用存储在myTypevariables中的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>() { //... } } 

您需要使用reflection来获取开始的方法,然后通过使用MakeGenericMethod提供types参数来“构build”它:

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

对于一个静态方法,将null作为第一个parameter passing给Invoke 。 这与generics方法无关 – 这只是普通的reflection。

如上所述,使用dynamic的C#4中的很多function都比较简单 – 当然,如果您可以使用types推断。 这对于types推断不可用的情况没有帮助,例如问题中的确切示例。

只是原来的答案的补充。 虽然这将工作:

 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做任何事情,这段代码将继续工作,或者至less在编译时断开(如果你删除了GenericMethod )。

其他的方法来创build一个新的包装类,并通过Activator创build它。 我不知道有没有更好的办法。

使用dynamictypes而不是reflectionAPI,可以大大简化调用带有只在运行时已知的types参数的generics方法。

要使用这种技术,必须从实际的对象中知道types(不只是Type类的一个实例)。 否则,您必须创build该types的对象或使用标准reflectionAPI 解决scheme 。 您可以使用Activator.CreateInstance方法创build一个对象。

如果你想调用一个通用的方法,那么在“正常”使用情况下会有它的types推断,那么它只是将未知types的对象投射到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 

Process是一种通用的实例方法,它通过使用GetType()方法)和通用参数的types(通过使用typeof运算符)来写入传递参数的实际types。

通过将对象参数转换为dynamictypes,我们推迟提供types参数直到运行时。 当使用dynamic参数调用Process方法时,编译器不关心这个参数的types。 编译器生成的代码在运行时检查传递参数的实际types(通过使用reflection),并select最佳的方法来调用。 这里只有这一个generics方法,所以它被调用一个适当的types参数。

在这个例子中,输出和你写的一样:

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

dynamictypes的版本确实更短,更容易编写。 你也不用担心多次调用这个函数的性能。 下一次调用相同types的参数应该会更快,这要归功于DLR中的caching机制。 当然,你可以编写caching被调用委托的代码,但是通过使用dynamictypes,你可以免费得到这个行为。

如果要调用的generics方法没有参数化types的参数(因此无法推断其types参数),那么可以将generics方法的调用包装在一个辅助方法中,如下例所示:

 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对象作为使用reflectionAPI的替代品的真正好处是,只有在运行时才会失去对这种特定types的编译时检查。 其他参数和方法的名称像平常一样由编译器进行静态分析。 如果你删除或添加更多的参数,改变他们的types或重命名方法名称,那么你会得到一个编译时错误。 如果将方法名称作为Type.GetMethod的string提供,并将参数作为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); } } 

在这里我们再次通过将参数转换为dynamictypes来执行一些方法。 只有第一个参数types的validation被推迟到运行时。 如果您调用的方法的名称不存在,或者其他参数无效(参数的错误数量或types错误),则会发生编译器错误。

当你将dynamicparameter passing给一个方法时,这个调用是最近被绑定的 。 方法重载parsing在运行时发生,并尝试select最佳的重载。 所以如果你用BarItemtypes的对象调用ProcessItem方法,那么你实际上会调用非generics方法,因为它是这种types的更好的匹配。 然而,当你传递一个Alphatypes的参数时,你会得到一个运行时错误,因为没有方法可以处理这个对象(一个generics方法有where T : IItemAlpha类没有实现这个接口的约束)。 但是,这是整个问题。 编译器没有这个调用有效的信息。 你作为程序员知道这一点,你应该确保这个代码运行没有错误。

返回typesgotcha

当你用一个dynamictypes的参数调用一个非void方法时,它的返回types也可能是dynamic 。 所以如果你想把前面的例子改成这个代码:

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

那么结果对象的types将是dynamic 。 这是因为编译器并不总是知道哪个方法会被调用。 如果你知道函数调用的返回types,那么你应该隐式地将它转换为所需的types,所以剩下的代码是静态types的:

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

如果types不匹配,您会得到一个运行时错误。

实际上,如果您尝试在前面的示例中获取结果值,则会在第二次循环迭代中获得运行时错误。 这是因为你试图保存一个void函数的返回值。

在C#4.0中,reflection是不必要的,因为DLR可以使用运行时types来调用它。 由于使用DLR库是一种dynamic的痛苦(而不是C#编译器为您生成代码),所以开源框架Dynamitey (PCL)为您提供了轻松的caching运行时访问,以便编译器为您生成相同的调用。

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

再加上Adrian Gallero的回答 :

从types信息调用通用方法涉及三个步骤。

TLDR:使用types对象调用已知的generics方法可以通过以下方法完成:

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

其中GenericMethod<object>是要调用的方法名称,以及任何满足通用约束的types。

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

第一步是获取通用方法定义的MethodInfo

方法1:使用GetMethod()或GetMethods()与适当的types或绑定标志。

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

方法2:创build一个委托,获取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”实际上指的是一组可能包含一个或多个方法的方法。 在提供方法参数的types之前,不知道您指的是哪种方法。

((Action)GenericMethod<object>)引用特定方法的委托。 ((Func<string, int>)GenericMethod<object>)引用GenericMethod的不同重载

方法3:创build一个包含方法调用expression式的lambdaexpression式,获取MethodInfo对象,然后GetGenericMethodDefinition

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

这分解成

创build一个lambdaexpression式,其中正文是对所需方法的调用。

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

提取主体并转换为MethodCallExpression

 MethodCallExpression methodCallExpr = (MethodCallExpression)expr.Body; 

从方法中获取generics方法定义

 MethodInfo methodA = methodCallExpr.Method.GetGenericMethodDefinition(); 

第2步调用MakeGenericMethod来创build一个适当types的通用方法。

 MethodInfo generic = method.MakeGenericMethod(myType); 

第3步是用适当的参数调用该方法。

 generic.Invoke(this, null); 

没有人提供“ 经典reflection ”解决scheme,所以这里是一个完整的代码示例:

 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)

并创build并返回一个IDictionary实例,其键和值的types完全是在调用keyTypevalueType上指定的。

这里是一个完整的例子,如何调用这个方法来实例化和使用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的答案 ,这是我的两分钱,但有一个generics方法需要两个参数。

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

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

在我的情况下,U型始终是存储typesT的对象的可观察集合。

由于我有我的types预定义,我首先创build表示可观察的集合(U)和存储在它(T)中的对象的“虚拟”对象,并将调用Make时使用下面的types来获取它们的types

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

然后调用GetMethod来find你的通用函数:

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

到目前为止,上面的调用与上面解释的几乎完全相同,但是当需要将多个parameter passing给它时,差别很小。

您需要将Type []数组传递给包含上面创build的“虚拟”对象types的MakeGenericMethod函数:

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

一旦完成,你需要调用上面提到的Invoke方法。

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

你完成了。 发挥魅力!

更新:

正如@Bevan强调的,我不需要在调用MakeGenericMethod函数时创build一个数组,因为它需要参数,我不需要创build一个对象来获取types,因为我可以直接将types传递给这个函数。 在我的情况下,因为我有另一个类中预定义的types,我只是简单地将我的代码更改为:

 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属性,并且将为我提供我在MakeGenericMethod中使用的相关types。

再次感谢这个@Bevan。