[Investor Relations]  |  官方微博

.NET培训

美国上市公司 · 亿元级外企Java培训企业

  • 全国服务监督电话4008270010
.Net培训 > NET知识 > FrameWork反射调用的几种方式及性能测试
  • FrameWork反射调用的几种方式及性能测试

    发布:.Net培训  来源:NET知识  时间: 2017年02月24日

  • 主要因为近期看到很多同学、朋友都已经使用 VS2012 进行 .NET 4.5 开发了,却还在大量使用反射,不知道用新的方式。或有所了解,但又害怕性能不好不敢大胆去用。...

  • 园子里和这个话题的相关文章比较多,本文是旧话重提,外加个小的总结。主要因为近期看到很多同学、朋友都已经使用 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> 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>(body, math, a, b);

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

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

    var math = new MyMath();

    Expression> addExpTree = (a, b) => math.Add(a, b); // ExressionTreeFunc 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)dynamicMethod.CreateDelegate(typeof(Func));//var math = ew 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))。

    下面是几种方法的性能测试。

    性能对比测试

    这里对直接、反射发出、dynamic 、表达式树编译、反射五种调用方式进行性能对比测试。

    测试结果

    先给出测试的结果:

    【FrameWork反射调用的几种方式及性能测试】

    从上图中可以看出:

    直接调用性能最佳;

    反射发出和表达式树两种方式性能相当,速度接近直接调用;

    dynamic 性能居中,也不错;

    反射方式性能最差。

    另外说明两点:

    本次测试仅针对 MyMath.Add 方法,其参数和返回值都是值类型,反射调用时存在大量装箱、拆箱。如果测试方法的参数和返回值都是引用类型,反射方式与其它方式间的差距会小些。

    从上图可以看出这几次方式性能差别较大,但此结果是重复 100 万次的情况下得出的。考虑单次调用,反射只比直接调用慢 381 纳秒。如果你的代码不是位于循环的中心或是系统的瓶颈,调用次数不多,性能差异可以完全忽略。

    测试代码

    以下是测试用代码,仅参考:

    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 = ew 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 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>(body, math, a, b); return lambda.Compile();

    } static Func 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)dynamicMethod.CreateDelegate(typeof(Func));

    }

    }

    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(string message, Func func) { using (new Profiler(message)) return func();

    }

    }

    总结

    综上所述,我们可以使用 .NET 2.0 的 DynamicMethod,.NET 3.5 引入的 Expression Tree、.NET 4 新增的 dynamic 来替换反射调用,带来更好的性能。

    .net培训机构希望本文对大家有所帮助,也希望整天忙于项目、疲于工作的朋友抽点时间学习下 .NET Framework 的一些“新”

  • 上一篇:.Net Core 之 MSBuild 介绍

    下一篇:没有下一篇了

网站导航
2001-2016 达内时代科技集团有限公司 版权所有 京ICP证8000853号-56