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

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

性能对比测试

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

测试结果

先给出测试的结果:

从上图中可以看出:

  • 直接调用性能最佳;

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

  • dynamic 性能居中,也不错;

  • 反射方式性能最差。

另外说明两点:

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

  2. 从上图可以看出这几次方式性能差别较大,但此结果是重复 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 = 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 的一些“新”

原文地址:http://www.cnblogs.com/ldp615/archive/2013/03/31/2991304.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

替代反射调用的几种方式及性能测试相关推荐

  1. 通过反射创建对象的几种方式

    通过反射创建对象的几种方式 public static void main(String args []) throws Exception {//这个是获得cat类的类对象.Class catcls ...

  2. Dubbo 同步、异步调用的几种方式

    我们知道,Dubbo 缺省协议采用单一长连接,底层实现是 Netty 的 NIO 异步通讯机制:基于这种机制,Dubbo 实现了以下几种调用方式: 同步调用 异步调用 参数回调 事件通知 同步调用 同 ...

  3. java获取反射机制的三种方式

    更多免费教学文章请关注这里 java 获取反射常使用的三种方式: 1.通过new对象实现反射机制 2.通过路径实现反射机制 3.通过类名实现反射机制 通过Object类的getClass方法来获取 使 ...

  4. delphi 异步 调用 带参数_Dubbo 关于同步/异步调用的几种方式

    我们知道,Dubbo 缺省协议采用单一长连接,底层实现是 Netty 的 NIO 异步通讯机制:基于这种机制,Dubbo 实现了以下几种调用方式: 同步调用 异步调用 参数回调 事件通知 同步调用 同 ...

  5. DLL 文件调用的2种方式

    一个共享库导出给用户使用的类,符号,函数等都需要使用宏Q_DECL_EXPORT来定义导出, 一个使用共享库的应用程序需要通过定义Q_DECL_IMPORT 导入共享库里的可用对象. WIN32 平台 ...

  6. spring-cloud调用服务两种方式

    spring-cloud调用服务有两种方式,一种是Ribbon+RestTemplate, 另外一种是Feign. Ribbon是一个基于HTTP和TCP客户端的负载均衡器,其实feign也使用了ri ...

  7. python和c语言相通吗_python和C语言互相调用的几种方式

    1 2 3 4 5 6 7 8 9 版权申明:本文为博主窗户(Colin Cai)原创,欢迎转帖.如要转贴,必须注明原文网址 http://www.cnblogs.com/Colin-Cai/p/75 ...

  8. python和C语言互相调用的几种方式

    Python这些年风头一直很盛,占据了很多领域的位置,Web.大数据.人工智能.运维均有它的身影,甚至图形界面做的也很顺,乃至full-stack这个词语刚出来的时候,似乎就是为了描述它. Pytho ...

  9. (二)js在html中调用的三种方式

    1. 行内式 <input type="button" value="唐伯虎" οnclick="alert('秋香姐')"> ...

最新文章

  1. 【C++】C/C++ 中 static 的用法全局变量与局部变量
  2. 【Python】ubuntu14安装pycaffe环境:python2.7及依赖库
  3. Web开发的机器学习框架
  4. java 接口与抽象类的区别
  5. 青龙羊毛——东方头条(搬砖,非原创)
  6. 安卓linux定时执行脚本,Android开机自动执行shell脚本
  7. Linux Shell常用技巧(七)
  8. android 加载多个图片 内在溢出的问题
  9. java验证码画布类型,【Java工具类】使用Kaptcha生成验证码写回页面中
  10. vue ---- webpack扩展
  11. [树上倍增][最小生成树]JZOJ P4313——电话线铺设
  12. Eclipse插件大全 挑选最牛的TOP30
  13. Hamilton-Caylay (哈密尔顿-凯莱)定理
  14. python日历图_Python干货宝典!玩转内置模块:日历模块
  15. linux删除文件子最后两行,关于linux:如何使用sed删除文件的最后n行
  16. js二维码样式生成插件easy.qrcode.js
  17. 如何下载ArcGis Server离线地图瓦片数据
  18. 月赚千刀的国外联盟Lead项目,实操拆解
  19. 功能测试的类型之Alpha和Beta测试
  20. Android 仿微信添加群聊界面——addView

热门文章

  1. NATS服务器部署及测试
  2. Exchange Server2010系列之十一:部署CAS+HT角色高可用性
  3. 敏捷个人:提供更多文档下载,并转载一篇敏捷个人读书笔记
  4. 谈谈关于MVP模式中V-P交互问题
  5. .NET点滴:SpanT
  6. Dapr 集成 APISIX 做API网关
  7. .NET Core 如何调试 CPU 爆高?
  8. 如何在 ASP.NET CORE 中获取客户端 IP ?
  9. BenchmarkDotNet性能测试
  10. Xamarin.Forms: 无限滚动的ListView(懒加载方式)