How do I use reflection to call a generic method?
当类型参数在编译时未知,而在运行时动态获取时,调用泛型方法的最佳方法是什么?
考虑下面的示例代码-在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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>() { //... } } |
您需要使用反射来获取要开始使用的方法,然后通过使用MakeGenericMethod提供类型参数来"构造"该方法:
1 2 3 | MethodInfo method = typeof(Sample).GetMethod("GenericMethod"); MethodInfo generic = method.MakeGenericMethod(myType); generic.Invoke(this, null); |
对于静态方法,将
如前所述,如果您可以使用类型推断,那么使用
只是对原始答案的补充。虽然这会起作用:
1 2 3 | MethodInfo method = typeof(Sample).GetMethod("GenericMethod"); MethodInfo generic = method.MakeGenericMethod(myType); generic.Invoke(this, null); |
这也有点危险,因为您会丢失对
因此,如果您知道在编译时链接到的方法,并且这不是数百万次调用,因此开销无关紧要,我将把此代码更改为:
1 2 3 4 5 | 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); |
虽然不是很漂亮,但这里有对
另一种方法是创建一个新的包装类,并通过
通过使用
要使用此技术,必须从实际对象(而不仅仅是
如果您想调用一个泛型方法,在"normal"用法中会推断出它的类型,那么它只需将未知类型的对象强制转换为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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)" } } } |
下面是这个程序的输出:好的。
1 2 3 4 5 6 |
通过将对象参数强制转换为
在本例中,输出与编写时相同:好的。
1 2 3 4 5 6 |
具有动态类型的版本绝对更短,更容易编写。您也不必担心多次调用此函数的性能。由于DLR中的缓存机制,具有相同类型参数的下一个调用应该更快。当然,您可以编写缓存调用的委托的代码,但是通过使用
如果要调用的泛型方法没有参数化类型的参数(因此无法推断其类型参数),则可以将泛型方法的调用包装在助手方法中,如下例所示:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
提高类型安全性
使用
下面是一个简单的示例,说明了在编译时(注释代码)和运行时如何捕获某些错误。它还显示了DLR如何尝试解析要调用的方法。好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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); } } |
在这里,我们再次通过将参数强制转换为
当您将
当使用动态类型的参数调用非void方法时,其返回类型也可能是
1 | var result = ProcessItem((dynamic)testObjects[i],"test" + i, i); |
那么结果对象的类型将是
1 | string result = ProcessItem((dynamic)testObjects[i],"test" + i, i); |
如果类型不匹配,您将得到一个运行时错误。好的。
实际上,如果您尝试在前一个示例中获取结果值,那么在第二个循环迭代中会得到一个运行时错误。这是因为您试图保存void函数的返回值。好的。好啊。
对于C 4.0,反射是不必要的,因为DLR可以使用运行时类型来调用它。由于动态地使用DLR库(而不是为您生成代码的C编译器),因此开源框架动态(.NET标准1.5)为您提供了对编译器将为您生成的相同调用的轻松缓存运行时访问。
1 2 3 4 5 6 |
加上阿德里安·加莱罗的回答:
从类型信息调用泛型方法涉及三个步骤。
tldr:使用类型对象调用已知的泛型方法可以通过以下方式完成:1 2 3 4 5 | ((Action)GenericMethod<object>) .Method .GetGenericMethodDefinition() .MakeGenericMethod(typeof(string)) .Invoke(this, null); |
其中,
(action)匹配要调用的方法的签名,即(
1 |
方法2:创建委托,获取MethodInfo对象,然后调用GetGenericMethodDefinition
从包含方法的类内部:
1 2 3 4 5 6 7 | MethodInfo method = ((Action)GenericMethod<object>) .Method .GetGenericMethodDefinition(); MethodInfo method = ((Action)StaticMethod<object>) .Method .GetGenericMethodDefinition(); |
从包含方法的类外部:
1 2 3 4 5 6 7 8 | MethodInfo method = ((Action)(new Sample()) .GenericMethod<object>) .Method .GetGenericMethodDefinition(); MethodInfo method = ((Action)Sample.StaticMethod<object>) .Method .GetGenericMethodDefinition(); |
在C中,方法的名称,即"ToString"或"GenericMethod"实际上是指可能包含一个或多个方法的一组方法。在提供方法参数的类型之前,不知道您所指的方法。
1 2 3 | MethodInfo method = ((MethodCallExpression)((Expression<Action<Sample>>)( (Sample v) => v.GenericMethod<object>() )).Body).Method.GetGenericMethodDefinition(); |
这可以归结为
创建一个lambda表达式,其中主体是对所需方法的调用。
1 | Expression<Action<Sample>> expr = (Sample v) => v.GenericMethod<object>(); |
提取主体并强制转换为方法calExpression
1 | MethodCallExpression methodCallExpr = (MethodCallExpression)expr.Body; |
从方法中获取泛型方法定义
1 | MethodInfo methodA = methodCallExpr.Method.GetGenericMethodDefinition(); |
步骤2正在调用MakeGenericMethod以创建具有适当类型的泛型方法。
1 | MethodInfo generic = method.MakeGenericMethod(myType); |
步骤3正在使用适当的参数调用该方法。
1 | generic.Invoke(this, null); |
没有人提供"经典反射"解决方案,因此这里有一个完整的代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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; } } } |
上面的
它创建并返回一个IDictionary实例,其键和值的类型与调用
下面是一个完整的例子,如何调用这个方法来实例化和使用一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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"); } } } |
当执行上述控制台应用程序时,我们得到正确的预期结果:
1 2 3 4 | Dictionary<String, int> "One": 1 "Two": 2 "Three": 3 |
这是我的2美分,基于Grax的答案,但有两个通用方法所需的参数。
假设您的方法在助手类中定义如下:
1 2 3 4 5 6 7 8 | public class Helpers { public static U ConvertCsvDataToCollection<U, T>(string csvData) where U : ObservableCollection<T> { //transform code here } } |
在我的例子中,u类型始终是一个可观察的集合,存储t类型的对象。
由于我已经预先定义了类型,所以我首先创建了表示可观察集合(U)和存储在其中的对象(T)的"虚拟"对象,这些对象将在下面用于在调用make时获取其类型。
1 2 | object myCollection = Activator.CreateInstance(collectionType); object myoObject = Activator.CreateInstance(objectType); |
然后调用getmethod查找泛型函数:
1 2 |
到目前为止,上面的调用与上面解释的几乎完全相同,但是在需要向它传递多个参数时有一点不同。
需要将类型[]数组传递给包含上面创建的"dummy"对象类型的MakeGenericMethod函数:
1 2 3 4 5 | MethodInfo generic = method.MakeGenericMethod( new Type[] { myCollection.GetType(), myObject.GetType() }); |
完成后,您需要调用上面提到的invoke方法。
1 |
你完成了。作品魅力无穷!
更新:
正如@bevan强调的那样,当调用makeGenericMethod函数时,我不需要创建数组,因为它需要参数,而且我不需要创建对象来获取类型,因为我可以直接将类型传递给这个函数。在我的例子中,由于我在另一个类中预先定义了类型,所以我只需将代码更改为:
1 2 3 4 5 6 7 8 9 10 11 |
MyClassInfo包含2个类型为
再次感谢您突出显示@bevan。