园子里和这个话题的相关文章比较多,本文是旧话重提,外加个小的总结。主要因为近期看到很多同事、朋友都已经使用 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. 反射发出调用针对 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 表达从当前方法返回,并将返回值(如果存在)从调用方的计算堆栈推送到被调用方的计算堆栈上。
下面是几种方法的性能测试。 性能对比测试这里对直接、反射发出、dynamic 、表达式树编译、反射五种调用方式进行性能对比测试。 测试结果先给出测试的结果: 从上图中可以看出: 另外说明两点: 测试代码以下是测试用代码,仅参考: - using System;
- using System.Linq.Expressions;
- using System.Reflection.Emit;
- public class MyMath {
- public int Add(int a, int b) { return a + b; }
- }
- class Program {
- static void Main(string[] args) {
- int result;
- var math = new MyMath();
- var count = 1000000;
- Console.WriteLine("数据量:" + count);
- Console.WriteLine("-----------------------------r\n");
- using (Profiler.Step("循环:{0} ms")) {
- for (int i = 0; i < count; i++)
- result = 1;
- }
- using (Profiler.Step("直接调用 :{0} ms")) {
- for (int i = 0; i < count; i++)
- result = math.Add(i, i);
- }
- using (Profiler.Step("反射发出:{0} ms")) {
- var emitAdd = BuildEmitAddFunc();
- for (int i = 0; i < count; i++)
- result = emitAdd(math, i, i);
- }
- using (Profiler.Step("表达式树:{0} ms")) {
- var expressionAdd = BuildExpressionAddFunc();
- for (int i = 0; i < count; i++)
- result = expressionAdd(math, i, i);
- }
- using (Profiler.Step("dynamic 调用:{0} ms")) {
- dynamic d = math;
- for (int i = 0; i < count; i++)
- result = d.Add(i, i);
- }
- using (Profiler.Step("反射调用:{0} ms")) {
- var add = typeof(MyMath).GetMethod("Add");
- for (int i = 0; i < count; i++)
- result = (int)add.Invoke(math, new object[] { i, i });
- }
- Console.WriteLine("\r\n\r\n测试完成,任意键退出...");
- Console.ReadKey();
- }
- static Func<MyMath, int, int, int> BuildExpressionAddFunc() {
- 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(math, add, a, b);
- var lambda = Expression.Lambda<Func<MyMath, int, int, int>>(body, math, a, b);
- return lambda.Compile();
- }
- static Func<MyMath, int, int, int> BuildEmitAddFunc() {
- var add = 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, add);
- il.Emit(OpCodes.Ret);
- return (Func<MyMath, int, int, int>)dynamicMethod.CreateDelegate(typeof(Func<MyMath, int, int, int>));
- }
- }
复制代码Profiler 是我写的一个类,用于简化测试: - using System;
- using System.Diagnostics;
- public class Profiler : IDisposable {
- private Stopwatch watch;
- private string message;
- private Profiler(string message) {
- this.watch = new Stopwatch();
- this.watch.Start();
- this.message = message;
- }
- public void Dispose() {
- watch.Stop();
- Console.WriteLine(message, watch.ElapsedMilliseconds);
- Console.WriteLine();
- }
- public static IDisposable Step(string message) {
- return new Profiler(message);
- }
- public static T Inline<T>(string message, Func<T> func) {
- using (new Profiler(message))
- return func();
- }
- }
复制代码 总结综上所述,我们可以使用 .NET 2.0 的 DynamicMethod,.NET 3.5 引入的 Expression Tree、.NET 4 新增的 dynamic 来替换反射调用,带来更好的性能。 希望本文对大家有所帮助,也希望整天忙于项目、疲于工作的朋友抽点时间学习下 .NET Framework 的一些“新”特性。
|