问题回顾

  在上篇博客中,我介绍了优化反射的第一个步骤:用委托调用代替直接反射调用。
  然而,那只是反射优化过程的开始,因为新的问题出现了:如何保存大量的委托?

  如果我们将委托保存在字典集合中,会发现这种设计会浪费较多的执行时间,因为这种设计会引发三个新问题:
  1. 代码的执行路径变长了。
  2. 字典查找是有成本开销的。
  3. 字典集合的并发读写需要锁定,会影响并发性。

  再来回顾一下上次的测试结果吧:

  虽然通用接口ISetValue将反射性能优化了37倍,但是最终的FastSetValue将这个数字减少到还不到7倍(在CLR4中还不到5倍)。
  难道您不觉得遗憾吗?

  再看看直接调用与反射调用的对比,它们的速度相差了上千倍!

  能不能不使用委托?

  既然委托最后引出了三个难以解决的问题,导致优化后速度比直接调用差距太远,那我们能不能不使用委托呢?

  委托调用并不是优化反射的唯一方案,我们还有其它方法,
  之所以委托调用能成为常见的优化方案是因为它比较简单。

  假如我需要用客户端提交的数据来填充某个数据对象,考虑到代码的通用性,我会用反射写成这样:

  1. /// <summary>
  2. /// 从HttpRequest加载obj所需的数据
  3. /// </summary>
  4. /// <param name="request"></param>
  5. /// <param name="obj"></param>
  6. public static void LoadDataFromHttpRequest(HttpRequest request, object obj)
  7. {
  8. PropertyInfo[] properties = obj.GetType().GetProperties();
  9. foreach( PropertyInfo p in properties ) {
  10. // 这里只是示意代码,假设数据处理不会有异常。
  11. object val = Convert.ChangeType(request[p.Name], p.PropertyType);
  12. p.FastSetValue(obj, val);
  13. }
  14. }

  如果我事先知道要加载已知的数据类型,代码会写成这样:

  1. public static void LoadDataFromHttpRequest(HttpRequest request, OrderInfo order)
  2. {
  3. // 这里只是示意代码,假设数据处理不会有异常。
  4. order.OrderID = int.Parse(request["OrderID"]);
  5. order.OrderDate = DateTime.Parse(request["OrderDate"]);
  6. order.SumMoney = decimal.Parse(request["SumMoney"]);
  7. order.Comment = request["Comment"];
  8. order.Finished = bool.Parse(request["Finished"]);
  9. }

  显然,第二段代码运行效率更快(尽管第一段代码调用FastSetValue优化了速度)。

  大家都知道反射性能较差,直接调用性能最好,那么能不能在运行时不使用反射呢?

  的确,使用反射是因为我们事先不知道要处理哪些类型的对象,因此不得不用反射, 另外,反射的代码也更通用,写一个方法可以加载所有的数据类型,可认为是一劳永逸的方法。 不过,就算我们事先不知道要处理哪些对象类型,但是只要使用反射,我们完全可以知道任何一个类型包含哪些数据成员, 还能知道这些数据成员的数据类型,这一点不用怀疑吧? 既然我们用反射可以知道所有的类型定义信息,我们是否可以参照代码生成器的思路去生成代码呢? 我们可以参照前面第二段代码,为【需要处理的类型】生成直接调用的代码,这样不就彻底解决了反射性能问题了吗? 生成代码的过程,其实也就是个字符串的拼接过程,难度并不大,只是比较复杂而已。

  如果前面的答案都是肯定的,那么现在只有一个问题了:我们能在运行时执行拼接生成的字符串代码吗?

  答案也是肯定的:能!

  CodeDOM:在运行时编译代码

  回忆一下我们编写的ASPX页面,它们并不是C#代码,它们本质上就是一个文本文件, 我们可以写入一些HTML标签,还有些标签上加了 runat="server" 属性, 我们还可以在页面中插入一些C#代码片段,尽管它们不是我们编译后的DLL文件,然而它们就是运行起来了! 要知道ASP.NET不是ASP,ASP是解释性的脚本语言,而ASP.NET是以编译方式运行的, 所以,每个ASPX页面文件最后都是运行编译后的结果。

  假设我有下面一段文本(文本的内容是一段C#代码):

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Reflection;
  5.  
  6. namespace OptimizeReflection
  7. {
  8. public class DemoClass
  9. {
  10. public int Id { get; set; }
  11.  
  12. public string Name;
  13.  
  14. public int Add(int a, int b)
  15. {
  16. return a + b;
  17. }
  18. }
  19.  
  20. public class 用户手册
  21. {
  22. public static void Main()
  23. {
  24. // OptimizeReflection 这个类库提供了一些扩展方法,它们用于优化常见的反射场景
  25. // 下面是一些相关的演示示例。
  26.  
  27. // 对于属性的读写操作、方法的调用操作,还提供了性能更好的强类型(泛型)版本,可参考Program.cs
  28.  
  29. Type instanceType = typeof(DemoClass);
  30. PropertyInfo propertyInfo = instanceType.GetProperty("Id");
  31. FieldInfo fieldInfo = instanceType.GetField("Name");
  32. MethodInfo methodInfo = instanceType.GetMethod("Add");
  33.  
  34. // 1. 创建实例对象
  35. DemoClass obj = (DemoClass)instanceType.FastNew();
  36.  
  37. // 2. 写属性
  38. propertyInfo.FastSetValue(obj, 123);
  39. propertyInfo.FastSetValue2(obj, 123);
  40.  
  41. // 3. 读属性
  42. int a = (int)propertyInfo.FastGetValue(obj);
  43. int b = (int)propertyInfo.FastGetValue2(obj);
  44.  
  45. // 4. 写字段
  46. fieldInfo.FastSetField(obj, "Fish Li");
  47.  
  48. // 5. 读字段
  49. string s = (string)fieldInfo.FastGetValue(obj);
  50.  
  51. // 6. 调用方法
  52. int c = (int)methodInfo.FastInvoke(obj, 1, 2);
  53. int d = (int)methodInfo.FastInvoke2(obj, 3, 4);
  54.  
  55. Console.WriteLine("a={0}; b={1}; c={2}; d={3}; s={4}", a, b, c, d, s);
  56. }
  57. }
  58. }

  您可以把上面这段文本想像成前面第二个版本的LoadDataFromHttpRequest方法,如果我们在运行时使用反射也能生成那样的代码, 现在就差把它编译成程序集了。下面的代码演示了如何将一段文本编译成程序集的过程:

  1. string code = null;
  2.  
  3. // 1. 生成要编译的代码。(示例为了简单直接从程序集内的资源中读取)
  4. Stream stram = typeof(CodeDOM).Assembly
  5. .GetManifestResourceStream("TestOptimizeReflection.用户手册.txt");
  6. using( StreamReader sr = new StreamReader(stram) ) {
  7. code = sr.ReadToEnd();
  8. }
  9.  
  10. //Console.WriteLine(code);
  11.  
  12. // 2. 设置编译参数,主要是指定将要引用哪些程序集
  13. CompilerParameters cp = new CompilerParameters();
  14. cp.GenerateExecutable = false;
  15. cp.GenerateInMemory = true;
  16. cp.ReferencedAssemblies.Add("System.dll");
  17. cp.ReferencedAssemblies.Add("OptimizeReflection.dll");
  18.  
  19. // 3. 获取编译器并编译代码
  20. // 由于我的代码使用了【自动属性】特性,所以需要 C# .3.5版本的编译器。
  21. // 获取与CLR匹配版本的C#编译器可以这样写:CodeDomProvider.CreateProvider("CSharp")
  22.  
  23. Dictionary<string, string> dict = new Dictionary<string, string>();
  24. dict["CompilerVersion"] = "v3.5";
  25. dict["WarnAsError"] = "false";
  26.  
  27. CSharpCodeProvider csProvider = new CSharpCodeProvider(dict);
  28. CompilerResults cr = csProvider.CompileAssemblyFromSource(cp, code);
  29.  
  30. // 4. 检查有没有编译错误
  31. if( cr.Errors != null && cr.Errors.HasErrors ) {
  32. foreach( CompilerError error in cr.Errors )
  33. Console.WriteLine(error.ErrorText);
  34.  
  35. return;
  36. }
  37.  
  38. // 5. 获取编译结果,它是编译后的程序集
  39. Assembly asm = cr.CompiledAssembly;

  整个过程分为5个步骤,它们已用注释标识出来了,这里不再重复了。

  如何调用编译结果

  前面的代码把一段文本字符串编译成了程序集,现在还有最后一个问题:如何调用编译结果?

  答案:有二种方法,
  1. 直接调用方法。
  2. 实例化程序集中的类型,以接口方式调用方法。
  其实这二种方法都需要使用反射,用反射定位到要调用的类型和方法。

  第一种方法要求在生成代码时,生成的类名和方法名是明确的,在调用方法时,我们有二个选择:
  1. 用反射的方式调用(这里只是一次反射)。
  2. 为方法生成委托(用上篇博客介绍的方法),然后基于委托调用。

  第二种方法要求在生成代码时,首先要定义一个接口,保证生成的代码能实现指定的接口,
  然而用反射找到要调用的类型名称,用反射或者委托调用构造方法创建类型实例,最后基于接口去调用。
  我们熟悉的ASPX页面就是采用了这种方式来实现的。

  这二种方法也可以这样区分:
  1. 如果生成的方法是静态方法,应该选择第一种方法。
  2. 如果生成的方法是实例方法,那么选择第二种方法是合理的。

  对于前面的示例,我采用了第一种方法了,因为类名和方法名称都是事先确定的而且实现起来比较简单。

  1. // 6. 找到目标方法,并调用
  2. Type t = asm.GetType("OptimizeReflection.用户手册");
  3. MethodInfo method = t.GetMethod("Main");
  4. method.Invoke(null, null);

  能不能不使用委托? 如何用好CodeDOM?
  在这篇博客中我不知道把它们安排在哪里较为合适,算了,还是把答案留给下篇博客吧。

C# 之 反射性能优化2的更多相关文章

  1. C# 之 反射性能优化1

    反射是一种很重要的技术,然而它与直接调用相比性能要慢很多,因此如何优化反射性能也就成为一个不得不面对的问题. 目前最常见的优化反射性能的方法就是采用委托:用委托的方式调用需要反射调用的方法(或者属性. ...

  2. C# 之 反射性能优化3

    阅读目录 开始 用Delegate优化反射的缺点 用Delegate优化反射的优点 用CodeDOM优化反射的优点 如何用好CodeDOM? 用CodeDOM优化反射的缺点 能不能不使用委托? 根据反 ...

  3. 反射(4)反射性能问题:直接调用vs反射调用

    很多人都说使用反射会有性能问题,那到底会比直接调用慢多少呢,下面就来测试一下. 直接调用vs反射调用 下面就来写个demo来验证下直接调用和反射调用的性能差异,代码如下: namespace Cons ...

  4. 如何利用缓存机制实现JAVA类反射性能提升30倍

    一次性能提高30倍的JAVA类反射性能优化实践 文章来源:宜信技术学院 & 宜信支付结算团队技术分享第4期-支付结算部支付研发团队高级工程师陶红<JAVA类反射技术&优化> ...

  5. java反射之-性能优化

    在最近的计划中,打算看看在不使用google protobuf的情况下,在原有的采用jackson作为json序列化工具的基础上,是否可以实现进一步的性能优化.主要是针对list的情况. 测试的时候选 ...

  6. 利用表达式树Expression优化反射性能

    最近做了一个.Net Core环境下,基于NPOI的Excel导入导出以及Word操作的服务封装,涉及到大量反射操作,在性能优化过程中使用到了表达式树,记录一下. Excel导入是相对比较麻烦的一块, ...

  7. 深入分析Java反射(八)-优化反射调用性能

    Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行Deb ...

  8. Unity性能优化(4)-官方教程Optimizing graphics rendering in Unity games翻译

    本文是Unity官方教程,性能优化系列的第四篇<Optimizing graphics rendering in Unity games>的翻译. 相关文章: Unity性能优化(1)-官 ...

  9. Android客户端性能优化(魅族资深工程师毫无保留奉献)

    本文由魅族科技有限公司资深Android开发工程师degao(嵌入式企鹅圈原创团队成员)撰写,是degao在嵌入式企鹅圈发表的第一篇原创文章,毫无保留地总结分享其在领导魅族多个项目开发中的Androi ...

随机推荐

  1. SQL Server Profiler 常见问题总结

    1.跟踪指定数据库 SELECT DB_ID('数据名称') 原文:https://jingyan.baidu.com/article/647f0115be128a7f2048a87d.html 2. ...

  2. windows系统yolov3的安装教程(图文)

    记于 2018-05-19 13:21:13 距离开始着手安装yolov3已经过去将近20个小时,当然我并没有装那么久啦,就是大概4,5个小时这么久,网络上教程很少,步骤也千奇百怪,这次成功装好后就想 ...

  3. spring aop -包的问题

    Caused by: java.lang.NoSuchMethodError: org.springframework.aop.framework.AopProxyUtils.getSingleton ...

  4. Javascript杂!

    JavaScript 标准参考教程(alpha) javascript中的 Object.defineProperty()和defineProperties JS压缩混淆  ---- 雅虎YUI 在线 ...

  5. Confluence 6 配置数据库查询超时时间

    如果数据库的查询时间太长同时你的应用程序显示没有响应,你可以配置数据库的查询超时时间.在默认情况下 Confluence 没有超时时间.希望配置数据库查询超时时间,在你的测试服务器上进行下面的操作: ...

  6. Android UiAutomator - CTS Frame

    使用UiAutomator进行UI自动化测试后,生成的测试结果并不是很美观.为了生成一份好看的测试结果(报告),本文将使用CTS框架,当然也可以自己编写一份测试报告框架(如:生成html,excel报 ...

  7. kafka架构浅显理解

    Kafka的概念: 1. AMQP协议 Advanced Message Queuing Protocol (高级消息队列协议) The Advanced Message Queuing Protoc ...

  8. 《剑指offer》二叉搜索树的后序遍历序列

    本题来自<剑指offer> 二叉搜索树的后序遍历序列 题目: 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是则输出Yes,否则输出No.假设输入的数组的任意两个数字 ...

  9. laravel 路由模型绑定

    我们在使用路由的时候一个很常见的使用场景就是根据资源 ID 查询资源信息: Route::get('task/{id}', function ($id) { $task = \App\Models\T ...

  10. PHP 方法,类与对象的相关函数学习

    1.function_exists function_exists(string)检测函数是否存在,string表示需要检测的函数名称(注意与property_exists,method_exists ...