园子里和这个话题的相关文章比较多,本文是旧话重提,外加个小的总结。主要因为近期看到很多同事、朋友都已经使用 VS2012 进行 .NET 4.5 开发了,却还在大量使用反射,不知道用新的方式。或有所了解,但又害怕性能不好不敢大胆去用。

  本文以如下类为例:

public class MyMath
{
public int Add(int a, int b)
{
return a + b;
}
}

  替代反射的几种方式

  倒序说吧,从先进简单的开始。

  1. dynamic 调用

  .NET 4 引入了 dynamic 类型,可以使用如下方式来完成对 MyMath.Add 方法的动态调用:

dynamic math = new MyMath();
int result = math.Add(1, 2);

  非常简单,效率也不错,可以看后面的性能对比测试结果。

  但有一点要注意, dynamic 遵守 .NET 的访问级别限定,会对成员进行可见性检查。也是说,只能 dynamic 调用 public 成员;当然,如果是同一程序集内部,internal 成员也是可以访问的。

  2. Expression Tree 编译调用

  Expression Tree 是 .NET 3.5 引入的。简单地,我们可以使用 lambda 构建一颗 Expression Tree:

var math = new MyMath();
Expression<Func<int, int, int>> add = (a, b) => math.Add(a, b);

  这种方法适合手工编码构建,还有另外一种方式可以动态构建:

var add = typeof(MyMath).GetMethod("Add");
var math = Expression.Parameter(typeof(MyMath));
var a = Expression.Parameter(typeof(int), "a");
var b = Expression.Parameter(typeof(int), "b");
var body = Expression.Call(myMath, add, a, b);
var lambda = Expression.Lambda<Func<MyMath, int, int, int>>(body, math, a, b);

  两种方式构建出的 Tree 是相同的。

  话归正题,构建出表达式树后,调用其 Compile 方法便可编译成一个委托,如下代码第 3 行:

var math = new MyMath();
Expression<Func<int, int, int>> addExpTree = (a, b) => math.Add(a, b);  // ExressionTree
Func<int, int, int> add = addExpTree.Compile();                         //  编译成委托
var result = add(1, 2);                                                 // 相加,结果为3

  与 dynamic 调用方法同,Expression Tree 编译出的委托方法也遵守 .NET 的访问级别限定,会对成员进行可见性检查,不能访问私有成员。

  3. 反射发出调用

  这里只介绍反射发出的一项技术 DynamicMethod,.NET 2.0 新增此类。

  使用 DynamicMethod 类在运行时定义轻量全局方法,然后使用委托执行这些方法。

  针对 MyMath.Add 方法,调用比前面两种方式复杂些:

var addMethod = typeof(MyMath).GetMethod("Add");
var dynamicMethod = new DynamicMethod("", typeof(int), new[] { typeof(MyMath), typeof(int), typeof(int) });
//
var il = dynamicMethod.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Callvirt, addMethod);
il.Emit(OpCodes.Ret);
//
var add = (Func<MyMath, int, int, int>)dynamicMethod.CreateDelegate(typeof(Func<MyMath, int, int, int>));
//
var math = new MyMath();
var result = add(math, 1, 2);

  从第 5 行起,使用几个 IL 汇编指令,简单一说:

  第 5 行,OpCodes.Ldarg_0 是将索引为 0 的参数值推送到堆栈上,Ldarg_1、Ldarg_2 以此类推;

  第 6 行,OpCodes.Callvirt 是调用对象的(后期绑定)方法,并且将返回值推送到计算堆栈上;

  第 9 行,OpCodes.Ret 表达从当前方法返回,并将返回值(如果存在)从调用方的计算堆栈推送到被调用方的计算堆栈上。

  反射发出是在汇编级别的,很底层,也意味着效率更高、威力更强大。反射发出能绕过跳过 JIT 可见性检查,访问 private 成员(对于 DynamicMethod 类,请查看:DynamicMethod 构造函数 (String, Type, Type[], Boolean))。