65.9K
CodeProject 正在变化。 阅读更多。
Home

动态调用泛型方法

starIconstarIconstarIconstarIconstarIcon

5.00/5 (17投票s)

2008年1月2日

GPL3

2分钟阅读

viewsIcon

103705

downloadIcon

1038

泛型反射可能很复杂。本文展示了如何使用 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:初始发布
© . All rights reserved.