好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

优化反射性能的总结(中)

优化反射性能的总结(中)

优化反射性能的总结(中)

优化反射性能的总结(中)

阅读目录

开始 问题回顾 能不能不使用委托? CodeDOM:在运行时编译代码 如何调用编译结果 招聘信息

回到顶部

问题回顾

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

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

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

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

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

回到顶部

能不能不使用委托?

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

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

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

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

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

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

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

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

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

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

答案也是肯定的:能!

回到顶部

CodeDOM:在运行时编译代码

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

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

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace OptimizeReflection
{
    public class DemoClass
    {
        public int Id { get; set; }

        public string Name;

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

    public class 用户手册
    {
        public static void Main()
        {
            // OptimizeReflection 这个类库提供了一些扩展方法,它们用于优化常见的反射场景
            // 下面是一些相关的演示示例。
            
            // 对于属性的读写操作、方法的调用操作,还提供了性能更好的强类型(泛型)版本,可参考Program.cs

            Type instanceType = typeof(DemoClass);
            PropertyInfo propertyInfo = instanceType.GetProperty("Id");
            FieldInfo fieldInfo = instanceType.GetField("Name");
            MethodInfo methodInfo = instanceType.GetMethod("Add");

            // 1. 创建实例对象
            DemoClass obj = (DemoClass)instanceType.FastNew();

            // 2. 写属性
            propertyInfo.FastSetValue(obj, 123);
            propertyInfo.FastSetValue2(obj, 123);

            // 3. 读属性
            int a = (int)propertyInfo.FastGetValue(obj);
            int b = (int)propertyInfo.FastGetValue2(obj);

            // 4. 写字段
            fieldInfo.FastSetField(obj, "Fish Li");

            // 5. 读字段
            string s = (string)fieldInfo.FastGetValue(obj);

            // 6. 调用方法
            int c = (int)methodInfo.FastInvoke(obj, 1, 2);
            int d = (int)methodInfo.FastInvoke2(obj, 3, 4);

            Console.WriteLine("a={0}; b={1}; c={2}; d={3}; s={4}", a, b, c, d, s);
        }
    }
}

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

 string  code  =   null ;

 // 1. 生成要编译的代码。(示例为了简单直接从程序集内的资源中读取)
  Stream  stram  =   typeof ( CodeDOM ) . Assembly
             . GetManifestResourceStream( "TestOptimizeReflection.用户手册.txt" );
 using (  StreamReader  sr  =   new   StreamReader (stram) ) {
    code  =  sr . ReadToEnd();
}

 //Console.WriteLine(code);

// 2. 设置编译参数,主要是指定将要引用哪些程序集
  CompilerParameters  cp  =   new   CompilerParameters ();
cp . GenerateExecutable  =   false ;
cp . GenerateInMemory  =   true ;
cp . ReferencedAssemblies . Add( "System.dll" );
cp . ReferencedAssemblies . Add( "OptimizeReflection.dll" );

 // 3. 获取编译器并编译代码
// 由于我的代码使用了【自动属性】特性,所以需要 C# .3.5版本的编译器。
// 获取与CLR匹配版本的C#编译器可以这样写:CodeDomProvider.CreateProvider("CSharp")

  Dictionary  <  string ,  string  >  dict  =   new   Dictionary  <  string ,  string  > ();
dict[ "CompilerVersion" ]  =   "v3.5" ;
dict[ "WarnAsError" ]  =   "false" ;

 CSharpCodeProvider  csProvider  =   new   CSharpCodeProvider (dict);
 CompilerResults  cr  =  csProvider . CompileAssemblyFromSource(cp, code);

 // 4. 检查有没有编译错误
  if ( cr . Errors  !=   null   &&  cr . Errors . HasErrors ) {
     foreach (  CompilerError  error  in  cr . Errors )
         Console  . WriteLine(error . ErrorText);

     return ;
}

 // 5. 获取编译结果,它是编译后的程序集
  Assembly  asm  =  cr . CompiledAssembly;

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

回到顶部

如何调用编译结果

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

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

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

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

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

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

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



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

回到顶部

招聘信息

公司需要若干名 .net 方面的高级开发人员,要求熟悉以下技术领域:
1. .net framework
2. ASP.NET
3. SQL SERVER (T-SQL, SP)
4. JavaScript, jQuery
5. CSS
6. 常见的设计模式。

说明:
1. 公司名称: 明源软件  
2. 工作地点:武汉。
3.  我只是负责【推荐】,具体细节请发邮件给我:liqifeng0503@163.com  
4. 咨询招聘相关的疑问也请发邮件给我,评论中的疑问一律不回复!

博客中所有代码将在后续博客中给出。

如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的 【 推荐 】按钮。
如果,您希望更容易地发现我的新博客,不妨点击一下右下角的 【 关注 Fish Li 】。
因为,我的写作热情也离不开您的肯定支持。

感谢您的阅读,如果您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是Fish Li 。

 

 

分类:  C#基础 ,  性能优化

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于优化反射性能的总结(中)的详细内容...

  阅读:43次