C# 如何使用反射调用泛型方法?
当类型参数在编译时未知,但在运行时动态获取时,调用泛型方法的最佳方法是什么 考虑以下示例代码-在示例方法中,使用myType变量中存储的类型调用GenericMethod的最简洁的方法是什么C# 如何使用反射调用泛型方法?,c#,.net,generics,reflection,C#,.net,Generics,Reflection,当类型参数在编译时未知,但在运行时动态获取时,调用泛型方法的最佳方法是什么 考虑以下示例代码-在示例方法中,使用myType变量中存储的类型调用GenericMethod的最简洁的方法是什么 public class Sample { public void Example(string typeName) { Type myType = FindType(typeName); // What goes here to call GenericM
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>()
{
//...
}
}
您需要使用反射来获取要开始的方法,然后通过提供以下类型参数来构造它: 对于静态方法,传递null作为要调用的第一个参数。这与泛型方法无关——它只是普通的反射
如前所述,与使用动态的C4相比,这要简单得多——当然,如果您可以使用类型推断的话。在类型推断不可用的情况下,例如问题中的确切示例,它没有帮助。只是对原始答案的补充。虽然这将起作用:
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执行任何操作,该代码将继续工作,或者至少在编译时中断(例如,如果您删除GenericMethod)
另一种方法是创建一个新的包装器类,并通过Activator创建它。我不知道是否有更好的方法。对于C4.0,反射是不必要的,因为DLR可以使用运行时类型来调用它。由于动态地使用DLR库而不是C编译器为您生成代码是一种痛苦,因此开源框架.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}));
通过使用类型而不是反射API,使用仅在运行时才知道的类型参数调用泛型方法可以大大简化 要使用此技术,必须从实际对象中知道类型,而不仅仅是类型类的实例。否则,必须创建该类型的对象或使用标准反射API。可以使用该方法创建对象 如果您想调用一个泛型方法,在正常使用中,该方法会推断出其类型,那么它只需要将未知类型的对象强制转换为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)"
}
}
}
Process是一个泛型实例方法,它使用GetType方法写入传递参数的实际类型,使用typeof运算符写入泛型参数的类型
通过将对象参数强制转换为动态类型,我们将提供类型参数推迟到运行时。当使用动态参数调用Process方法时,编译器不关心此参数的类型。编译器生成代码,在运行时使用反射检查传递参数的真实类型,并选择要调用的最佳方法。这里只有一个泛型方法,因此使用适当的类型参数调用它
在本例中,输出与您编写的相同:
foreach (var o in objects)
{
MethodInfo method = typeof(Service).GetMethod("Process");
MethodInfo generic = method.MakeGenericMethod(o.GetType());
generic.Invoke(service, new object[] { o });
}
带有动态类型的版本显然更短,更容易编写。您也不应该担心多次调用此函数的性能。由于DLR中的机制,具有相同类型参数的下一个调用应该更快。当然,您可以编写缓存调用的委托的代码,但是通过使用动态类型,您可以免费获得这种行为
如果要调用的泛型方法没有参数化类型的参数,因此无法推断其类型参数,则可以将泛型方法的调用封装在助手方法中,如以下示例所示:
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) + ">");
}
}
这里,我们通过将参数强制转换为动态类型,再次执行一些方法。只有第一个参数类型的验证推迟到运行时。如果您正在调用的方法的名称不存在,或者如果其他参数无效,或者参数数量错误或类型错误,则会出现编译器错误
当您将动态参数传递给一个方法时,此调用将被终止。方法重载解析发生在运行时,并尝试选择最佳重载。因此,如果您使用BarItem类型的对象调用ProcessItem方法,那么实际上您将调用非泛型方法,因为它更适合此类型。但是,当您传递Alpha类型的参数时,会出现运行时错误,因为没有方法可以处理此对象。泛型方法具有约束,其中T:IItem和Alpha类不实现此接口。但这就是重点。公司
iler没有此调用有效的信息。作为一名程序员,您知道这一点,并且应该确保此代码运行时没有错误
返回类型:gotcha
当您使用动态类型的参数调用非void方法时,其返回类型可能会改变。因此,如果您将前面的示例更改为以下代码:
var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
那么结果对象的类型将是动态的。这是因为编译器并不总是知道将调用哪个方法。如果知道函数调用的返回类型,则应将其转换为所需类型,以便代码的其余部分为静态类型:
string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
如果类型不匹配,将出现运行时错误
实际上,如果您在上一个示例中尝试获取结果值,那么在第二个循环迭代中会出现运行时错误。这是因为您试图保存void函数的返回值。添加到:
从类型信息调用泛型方法涉及三个步骤
TLDR:使用类型对象调用已知泛型方法可以通过以下方式完成:
方法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();
从方法中获取泛型方法定义
MethodInfo methodA = methodCallExpr.Method.GetGenericMethodDefinition();
步骤2调用MakeGenericMethod以创建具有适当类型的泛型方法。
第3步是使用适当的参数调用该方法。
这是我的2美分基于,但有两个参数需要一个通用的方法 假设您的方法在Helpers类中定义如下:
public class Helpers
{
public static U ConvertCsvDataToCollection<U, T>(string csvData)
where U : ObservableCollection<T>
{
//transform code here
}
}
然后调用GetMethod以查找泛型函数:
MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");
到目前为止,上面的调用与上面解释的调用几乎相同,但在需要向其传递多个参数时有一点不同
您需要将类型[]数组传递给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个类型为type的属性,我在运行时根据传递给构造函数的枚举值设置这些属性,并将为我提供相关类型,然后在MakeGenericMethod中使用这些类型
再次感谢您突出显示这个@Bevan。没有人提供经典的反射解决方案,因此下面是一个完整的代码示例:
当执行上述控制台应用程序时,我们得到了正确的预期结果:
Dictionary<String, int>
"One": 1
"Two": 2
"Three": 3
灵感来自-假设您有两个或更多的类,如
public class Bar { }
public class Square { }
您希望使用Bar和Square调用方法Foo,该方法声明为
public class myClass
{
public void Foo<T>(T item)
{
Console.WriteLine(typeof(T).Name);
}
}
这对每个班级都有效。在这种情况下,它将输出:
方格
酒吧
在使用反射调用方法的情况下,通常方法名本身由另一个方法发现。事先知道方法名称并不常见。我同意反射的常见用法。但最初的问题是如何调用GenericMethod如果语法允许,我们根本不需要GetMethod。但是对于这个问题,我该如何编写GenericMethod呢?我认为答案应该包括一种避免丢失GenericMethod的编译时链接的方法。现在,我不知道这个问题是否常见,但我确实知道我昨天遇到了这个问题,这就是为什么我要问这个问题。你可以用GenMethod.Method.GetGenericMethodDefinition代替这个。GetType.GetMethodGenMethod.Method.Name。它稍微更干净,可能更安全。在您的示例中myType是什么意思?现在您可以使用nameofGenericMethod+1;请注意,默认情况下GetMethod只考虑公共实例方法,因此,您可能需要BindingFlags.Static和/或BindingFlags.NonPublic。正确的标志组合是BindingFlags.NonPublic | BindingFlags.Instance和可选的BindingFlags.Static。一个被标记为dupe的问题想知道如何使用静态方法实现这一点,从技术上讲,这里的问题也是如此。调用静态方法时,Invoke的第一个参数应为null。第一个参数只有在调用实例方法时才是必需的。@ChrisMoschini:在答案中添加了它。@gzou:我在答案中添加了一些内容-但是请注意,对于调用问题中的泛型方法,dynamic没有帮助,因为类型推断不可用。编译器无法使用任何参数来确定类型参数。我尝试了Jon的解决方案,直到我在类中公开了泛型方法,它才能够工作。我知道另一个Jon回答说您需要指定bindingflags,但这没有帮助。您还需要BindingFl
ags.Instance,而不仅仅是BindingFlags.NonPublic,以获取私有/内部方法。这个问题的现代版本:@Peter Mortensen-fyi在“?”之前使用空格来分隔英语部分和非英语C部分;我不知道是谁把空间移走了,让它看起来像?是代码的一部分。如果没有代码,我当然同意删除空格,但在这种情况下…我们可以定义一个泛型方法,然后使用GetMethod方法获取泛型方法的所有信息并使用它,当您传递Alpha类型的参数时,将出现运行时错误,因为没有方法可以处理此对象。如果我调用var a=new Alpha ProcessItema,test+I,那么为什么通用ProcessItem方法不能有效地处理这个问题,并输出通用的流程项?@AlexEdelstein我编辑了我的答案以澄清一点。这是因为泛型ProcessItem方法具有泛型约束,并且只接受实现IItem接口的对象。当您调用ProcessItemnew Aplha时,test,1;或ProcessItemobjectnew Aplha,test,1;你会得到一个编译器错误,但当转换为dynamic时,你会将检查推迟到运行时。很好的答案和解释,对我来说非常合适。比公认的答案好得多,编写时间更短,性能更高,更安全;也不需要创建实例来获取类型-methodInfo.MakeGenericMethodTypeOfCollection,TypeOfObject就足够了。
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);
public class Helpers
{
public static U ConvertCsvDataToCollection<U, T>(string csvData)
where U : ObservableCollection<T>
{
//transform code here
}
}
object myCollection = Activator.CreateInstance(collectionType);
object myoObject = Activator.CreateInstance(objectType);
MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");
MethodInfo generic = method.MakeGenericMethod(
new Type[] {
myCollection.GetType(),
myObject.GetType()
});
generic.Invoke(null, new object[] { csvData });
object myCollection = null;
MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");
MethodInfo generic = method.MakeGenericMethod(
myClassInfo.CollectionType,
myClassInfo.ObjectType
);
myCollection = generic.Invoke(null, new object[] { csvData });
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;
}
}
}
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
public class Bar { }
public class 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 });
}
}
var objSquare = new Square();
objSquare.InvokeFoo();
var objBar = new Bar();
objBar.InvokeFoo();