动态调用泛型方法





5.00/5 (17投票s)
泛型反射可能很复杂。本文展示了如何使用 DynamicMethod 类来动态调用泛型方法。
引言
几天前,我需要动态地调用一个类中的方法。仅说这一点,事情看起来很简单。
像往常一样,事情变得复杂了……我发现该方法是一个泛型方法,并且该类为给定的方法定义了多个重载。
背景
在承担这项新任务时,我显然关心解决方案的性能输出,因此我选择了 .NET Framework 2.0 中新增的 DynamicMethod
类和 Reflection Emit。
网上有许多页面将 DynamicMethod
方法与 Reflection Invoke 方法进行比较,前者性能提升非常大(请阅读 这篇文章)。
要使用 DynamicMethod
类方法,必须能够生成方法的 IL 代码。我对此有一些了解,但你们都可以做到。只需用 C# 或其他 .NET 语言编写你的方法,编译它,然后使用 Lutz Roeder 的 .NET Reflector 查看生成的 IL。只要坚持不懈,你就能完成这项工作。
解决方案
开发成果是一个简单的静态类,其中包含几个公共方法,允许你为任何泛型方法创建 GenericInvoker
。
GenericInvoker
是一个定义如下的委托。
注意:对于没有返回值的方法,调用委托将始终返回 null
。
public delegate object GenericInvoker(object target, params object[] arguments);
你可以通过简单地调用 DynamiMethods
静态类上的 GenericMethodInvokerMethod
方法的其中一个重载来创建 GenericInvoker
委托的实例(包含在文章源代码存档中)。
注意:GenericInvoker
委托的创建可能是一个缓慢的过程。因此,如果你要在某种循环中使用它,你应该考虑缓存该值以减少性能影响。
Using the Code
以下是如何使用提供的 DynamicMethods
类的一个简单示例
// sample class
public class SampleClass {
private string instanceName;
public SampleClass(string instanceName) {
this.instanceName = instanceName;
}
public void Test<TType>(TType s) {
MessageBox.Show(string.Format("{0} From {1}", s, this.instanceName));
}
public string Concatenate<TType>(TType lhs, TType rhs) {
return string.Format("{0}{1}", lhs, rhs);
}
public string Concatenate<TType>(string prefix, TType lhs, TType rhs) {
return string.Format("{0} - {1}{2}", prefix, lhs, rhs);
}
}
// Tests class
public class Tests {
public void Tests() {
SampleClass instance = new SampleClass("Instance 1");
GenericInvoker invoker;
// invoke method that returns void
invoker = DynamicMethods.GenericMethodInvokerMethod(typeof(SampleClass), "Test",
new Type[] { typeof(string) });
ShowResult(invoker(instance, "this is a tests"));
// invoke method that returns string, the parameter types are used to find
// the correct overload
invoker = DynamicMethods.GenericMethodInvokerMethod(typeof(SampleClass),
"Concatenate", new Type[] { typeof(int) },
new Type[] { typeof(int), typeof(int) });
ShowResult(invoker(instance, 100, 200));
// invoke method that returns string, the parameter types are used to find
// the correct overload
invoker = DynamicMethods.GenericMethodInvokerMethod(typeof(SampleClass),
"Concatenate", new Type[] { typeof(int) },
new Type[] { typeof(string), typeof(int), typeof(int) });
ShowResult(invoker(instance, "PREFIX", 100, 200));
}
// show GenericInvoker result
private static void ShowResult(object result) {
if (null == result) {
MessageBox.Show("return is null");
} else {
MessageBox.Show(string.Format("return is {0}", result));
}
}
}
关注点
我面临的主要问题之一与 Type
类中的 GetMethod
方法有关。
虽然 GetMethod
对普通类型方法效果很好,但对泛型方法却不然。如果泛型方法有重载,那么 GetMethod
调用将始终返回 null
。
为了克服这个限制,我不得不使用 GetMethods
方法并遍历所有类型方法以获取正确的方法。以下是完成这项工作的代码
private static void FindMethod(Type type, string methodName, Type[] typeArguments,
Type[] parameterTypes, out MethodInfo methodInfo,
out ParameterInfo[] parameters) {
methodInfo = null;
parameters = null;
if (null == parameterTypes) {
methodInfo = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance);
methodInfo = methodInfo.MakeGenericMethod(typeArguments);
parameters = methodInfo.GetParameters();
} else {
// Method is probably overloaded. As far as I know there's no other way
// to get the MethodInfo instance, we have to
// search for it in all the type methods
MethodInfo[] methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance);
foreach (MethodInfo method in methods) {
if (method.Name == methodName) {
// create the generic method
MethodInfo genericMethod = method.MakeGenericMethod(typeArguments);
parameters = genericMethod.GetParameters();
// compare the method parameters
if (parameters.Length == parameterTypes.Length) {
for (int i = 0; i < parameters.Length; i++) {
if (parameters[i].ParameterType != parameterTypes[i]) {
continue; // this is not the method we're looking for
}
}
// if we're here, we got the right method
methodInfo = genericMethod;
break;
}
}
}
if (null == methodInfo) {
throw new InvalidOperationException("Method not found");
}
}
}
历史
- 2007.01.02:初始发布