在上一篇中我们列举了一些反射的常规的使用,这一篇我们将介绍一些关于关于反射的高级属性,这些包括创建对反射的性能的总结以及如何优化反射性能,以及通过InvokeMember的方法如何去调用反射等等,通过对这些内容的逐步熟悉,我们会对整个反射有一个更加深入的了解与认识,在文章的最后我们会附上一个小DEMO从而供以后查阅使用,通过对比几种不同的方法,我们来分别看各种方式进行操作的性能的差异,从而方便后续做出正确的选择,在下面的例子中我们将比较:直接访问花费的时间、采用EmitSet花费的时间、纯反射花费的时间、采用泛型委托花费的时间以及采用通用接口花费的时间,从而就这几种方式来做一组对比。

  首先我们需要封装一个Model从而供后面的代码进行调用

public class OrderInfo
{
public int OrderID { get; set; }
public DateTime OrderDate { get; set; }
public decimal SumMoney { get; set; }
public string Comment { get; set; }
public bool Finished { get; set; } public int Add(int a, int b)
{
return a + b;
}
}  

 1 首先是设置属性时进行的对比

  在进行最终的数据对比之前我们来看看这里面涉及到的几种调用方式分别表示什么?

  A  直接访问花费时间

    这个非常容易理解,就是直接new一个OrderInfo对象,然后对立面的各种属性进行读写操作,典型的面向对象的写法,这个是很好理解的,这里就不再赘述。

B  EmitSet花费时间

   这里需要看我们设置OrderInfo的属性值的时候是通过下面的这句代码来完成的:

SetValueDelegate setter2 = DynamicMethodFactory.CreatePropertySetter(propInfo);

  这里SetValueDelegate是一个委托,后面的工厂方法用于创建这个委托

public static SetValueDelegate CreatePropertySetter(PropertyInfo property)
{
if( property == null )
{
throw new ArgumentNullException("property");
}
if( !property.CanWrite )
{
return null;
}
MethodInfo setMethod = property.GetSetMethod(true); DynamicMethod dm = new DynamicMethod("PropertySetter", null,
new Type[] { typeof(object), typeof(object) },
property.DeclaringType, true); ILGenerator il = dm.GetILGenerator(); if( !setMethod.IsStatic )
{
il.Emit(OpCodes.Ldarg_0);
}
il.Emit(OpCodes.Ldarg_1); EmitCastToReference(il, property.PropertyType);
if( !setMethod.IsStatic && !property.DeclaringType.IsValueType )
{
il.EmitCall(OpCodes.Callvirt, setMethod, null);
}
else
{
il.EmitCall(OpCodes.Call, setMethod, null);
}
il.Emit(OpCodes.Ret); return (SetValueDelegate)dm.CreateDelegate(typeof(SetValueDelegate));
}

  这段代码确实不太容易理解,我们取其中的关键的部分来进行解释

  DynamicMethod:定义并表示一种可编译、执行和丢弃的动态方法。丢弃的方法可用于垃圾回收。具体的实例请参考这里

   dm.GetILGenerator():为该方法返回一个具有默认 MSIL 流大小(64 字节)的 Microsoft 中间语言 (MSIL) 生成器。

  il.Emit:将指定的指令放到指令流上。

  il.EmitCall:将 call 或 callvirt 指令放到 Microsoft 中间语言 (MSIL) 流上,以便调用 varargs 方法。关于Call和Callvirt的区别,参考其定义:

    Call:调用由传递的方法说明符指示的方法。

    Callvirt:对对象调用后期绑定方法,并且将返回值推送到计算堆栈上。

  在调用这些方法后,完成动态方法并创建一个可用于执行该方法的委托,通过这些委托来完成对OrderInfo这个对象的属性的赋值的过程。

  C 纯反射花费的时间

  这项就更好理解了,首先通过PropertyInfo propInfo = typeof(OrderInfo).GetProperty("OrderID");来获取到PropertyInfo对象,然后调用propInfo.SetValue(testObj, 123, null);来直接进行反射赋值,当然这种方法在进行大量的赋值时性能较差,这个可以通过后面的比较来进行说明。

  D 泛型委托花费的时间

  这种方法通过定义一个泛型类SetterWrapper<TTarget, TValue>,然后进行实例化,最后调用SetValue进行赋值。在SetterWrapper的构造函数中通过Delegate.CreateDelegate创建一个委托,委托对应的方法是MethodInfo m = propertyInfo.GetSetMethod(true);这种方法由于在实例化时确定了TTarget, TValue的类型,所以在最终的赋值的时候就没有了类型转换时的装箱和拆箱的操作,所以相对纯反射来说更快,但是相对于Emit这种直接操作MSIL方法来说,效果可能要差一些。

public class SetterWrapper<TTarget, TValue> : ISetValue
{
private Action<TTarget, TValue> _setter; public SetterWrapper(PropertyInfo propertyInfo)
{
if( propertyInfo == null )
throw new ArgumentNullException("propertyInfo"); if( propertyInfo.CanWrite == false )
throw new NotSupportedException("属性不支持写操作。"); MethodInfo m = propertyInfo.GetSetMethod(true);
_setter = (Action<TTarget, TValue>)Delegate.CreateDelegate(typeof(Action<TTarget, TValue>), null, m);
} public void SetValue(TTarget target, TValue val)
{
_setter(target, val);
}
void ISetValue.Set(object target, object val)
{
_setter((TTarget)target, (TValue)val);
}
}

  E 通用接口花费时间

  采用通用接口这种方式,能在一定程度上简化调用的方式,但缺点也是非常明显的,首先我们来看看接口中采用的定义:

public interface ISetValue
{
void Set(object target, object val);
}

  由于接口中定义的Set方法都是采用的Object作为参数,这在通用性上确实比较好,但是在由于传入的参数不可能都是Object类型,所以当传入值类型时就需要进行类型转换,进行拆箱操作,所以在进行大量操作时不可避免会有性能的损失,这个可以通过后面代码运行的时间来判断其优劣。

  另外由于ISetValue这一接口是在泛型类SetterWrapper<TTarget, TValue>中继承并实现的,所以在创建泛型类实例的时候,需要定义泛型类型,最终的实现是通过下面的代码来实现的

Type instanceType = typeof(SetterWrapper<,>).MakeGenericType(propertyInfo.DeclaringType, propertyInfo.PropertyType);
return (ISetValue)Activator.CreateInstance(instanceType, propertyInfo);   
public static ISetValue CreatePropertySetterWrapper(PropertyInfo propertyInfo)
{
if(propertyInfo == null)
{
throw new ArgumentNullException("propertyInfo");
}
if(propertyInfo.CanWrite == false)
{
throw new NotSupportedException("属性不支持写操作。");
}
MethodInfo mi = propertyInfo.GetSetMethod(true); if( mi.GetParameters().Length >1)
{
throw new NotSupportedException("不支持构造索引器属性的委托。");
}
if( mi.IsStatic )
{
Type instanceType = typeof(StaticSetterWrapper<>).MakeGenericType(propertyInfo.PropertyType);
return (ISetValue)Activator.CreateInstance(instanceType, propertyInfo);
}
else
{
Type instanceType = typeof(SetterWrapper<,>).MakeGenericType(propertyInfo.DeclaringType, propertyInfo.PropertyType);
return (ISetValue)Activator.CreateInstance(instanceType, propertyInfo);
}
}  

  F:FastSet花费时间

  这个实现方式在本质上是和通用接口实现方式一模一样的,唯一不同的是第一次创建实例后会缓存在一个Hashtable中,这样下次再获取当前实例的时候多了一个遍历Hashtable的过程了,所以最终会有一定的时间花费,其它的和通用接口的实现方法无异。

  G:FastSet2花费时间

  这个部分和FastSet比较起来的差别就是最终采用的是EmitSet的方式来实现的赋值过程,所以没有类型转换而且最终赋值的过程是直接对MSIL进行操作的,所以最后的时间也是比较短的。

   在介绍完这些内容后,我们通过下面的几个方法来进行10万次的重复实验过程,从而比较他们之间的差别,从而对整体有个直观的比较。

static void Test_SetProperty(int count)
{
OrderInfo testObj = new OrderInfo();
PropertyInfo propInfo = typeof(OrderInfo).GetProperty("OrderID"); Console.Write("直接访问花费时间: ");
Stopwatch watch1 = Stopwatch.StartNew(); for( int i = 0; i < count; i++ )
{
testObj.OrderID = 123;
} watch1.Stop();
Console.WriteLine(watch1.Elapsed.ToString()); SetValueDelegate setter2 = DynamicMethodFactory.CreatePropertySetter(propInfo);
Console.Write("EmitSet花费时间: ");
Stopwatch watch2 = Stopwatch.StartNew(); for( int i = 0; i < count; i++ )
{
setter2(testObj, 123);
} watch2.Stop();
Console.WriteLine(watch2.Elapsed.ToString()); Console.Write("纯反射花费时间:  ");
Stopwatch watch3 = Stopwatch.StartNew(); for( int i = 0; i < count; i++ )
{
propInfo.SetValue(testObj, 123, null);
}
watch3.Stop();
Console.WriteLine(watch3.Elapsed.ToString()); Console.Write("泛型委托花费时间: ");
SetterWrapper<OrderInfo, int> setter3 = new SetterWrapper<OrderInfo, int>(propInfo);
Stopwatch watch4 = Stopwatch.StartNew(); for(int i = 0; i < count; i++)
{
setter3.SetValue(testObj, 123);
}
watch4.Stop();
Console.WriteLine(watch4.Elapsed.ToString()); Console.Write("通用接口花费时间: ");
ISetValue setter4 = GetterSetterFactory.CreatePropertySetterWrapper(propInfo);
Stopwatch watch5 = Stopwatch.StartNew(); for( int i = 0; i < count; i++ )
{
setter4.Set(testObj, 123);
}
watch5.Stop();
Console.WriteLine(watch5.Elapsed.ToString()); propInfo.FastSetValue(testObj, 123);
Console.Write("FastSet花费时间:  ");
Stopwatch watch6 = Stopwatch.StartNew(); for( int i = 0; i < count; i++ )
propInfo.FastSetValue(testObj, 123); watch6.Stop();
Console.WriteLine(watch6.Elapsed.ToString()); propInfo.FastSetValue2(testObj, 123);
Console.Write("FastSet2花费时间:  ");
Stopwatch watch6b = Stopwatch.StartNew(); for( int i = 0; i < count; i++ )
propInfo.FastSetValue2(testObj, 123); watch6b.Stop();
Console.WriteLine(watch6b.Elapsed.ToString()); Hashtable table = new Hashtable();
table[propInfo] = new object();
Console.Write("Hashtable花费时间: ");
Stopwatch watch7 = Stopwatch.StartNew(); for( int i = 0; i < count; i++ )
{
object val = table[propInfo];
}
watch7.Stop();
Console.WriteLine(watch7.Elapsed.ToString()); Console.WriteLine("-------------------");
Console.WriteLine("纯反射花费时间({0}) / 直接访问花费时间({1}) = {2}",
watch3.Elapsed.ToString(),
watch1.Elapsed.ToString(),
watch3.Elapsed.TotalMilliseconds / watch1.Elapsed.TotalMilliseconds); Console.WriteLine("纯反射花费时间({0})/ 通用接口花费时间({1}) = {2}",
watch3.Elapsed.ToString(),
watch5.Elapsed.ToString(),
watch3.Elapsed.TotalMilliseconds / watch5.Elapsed.TotalMilliseconds); Console.WriteLine("纯反射花费时间({0}) / FastSet花费时间({1}) = {2}",
watch3.Elapsed.ToString(),
watch6.Elapsed.ToString(),
watch3.Elapsed.TotalMilliseconds / watch6.Elapsed.TotalMilliseconds);
}

  通过下面的截图,我们能够清晰看出每种访问方式之间的差异,了解了这些差异后我们能够在进行反射时采用一种最合理的方式来使用,同时我们也需要加深对Emit这种技术的理解,从而在改善软件性能方面有自己的方法。

  上面的示例仅仅是对OrderInfo的某一个属性进行赋值的操作时所作出的对比,在读取和创建类型实例的时候结果也是相同的,今天就介绍到这里,最后分享整个软件的DEMO,如果有需要可以点击进行下载。

谈谈对C#中反射的一些理解和认识(下)的更多相关文章

  1. 谈谈对C#中反射的一些理解和认识(上)

    今天就平常用到的非常多的反射这个技术来做一个总结,当然关于反射需要讲解的东西实在是太多的内容,在一片文章中想要讲解清楚是非常难的,本篇博客也是就自己本人对这些内容学习后的一个总结,当然包括看书和自己写 ...

  2. 转载 CSDN 谈谈我对证券公司一些部门的理解(前、中、后台)

    谈谈我对证券公司一些部门的理解(前.中.后台) 2018年02月08日 15:11:07 unirong 阅读数:2165   文中对各大部门的分析都是从作者多年经历总结出来的有感之谈,尤其是前台的6 ...

  3. 谈谈我对证券公司一些部门的理解(前、中、后台)[z]

    [z]https://blog.csdn.net/UniRong/article/details/79289947 文中对各大部门的分析都是从作者多年经历总结出来的有感之谈,尤其是前台的6大部门(经纪 ...

  4. 【原】谈谈对Objective-C中代理模式的误解

    [原]谈谈对Objective-C中代理模式的误解 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 这篇文章主要是对代理模式和委托模式进行了对比,个人认为Objective ...

  5. (转)谈谈RTP传输中的负载类型和时间戳

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://ticktick.blog.51cto.com/823160/350142 最近被 ...

  6. Golang的反射reflect深入理解和示例

    编程语言中反射的概念 在计算机科学领域,反射是指一类应用,它们能够自描述和自控制.也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examin ...

  7. 【FFMPEG】谈谈RTP传输中的负载类型和时间戳

    谈谈RTP传输中的负载类型和时间戳 最近被RTP的负载类型和时间戳搞郁闷了,一个问题调试了近一周,终于圆满解决,回头看看,发现其实主要原因还是自己没有真正地搞清楚RTP协议中负载类型和时间戳的含义.虽 ...

  8. [每日一题]面试官问:谈谈你对ES6的proxy的理解?

    [每日一题]面试官问:谈谈你对ES6的proxy的理解? 关注「松宝写代码」,精选好文,每日一题 作者:saucxs | songEagle 一.前言 2020.12.23 日刚立的 flag,每日一 ...

  9. Java反射的浅显理解

    一.回顾反射相关的知识 1.在xml文件中使用反射的好处: 1)代码更加灵活,后期维护只需要修改配置文件即可 · 初学者一般习惯于在代码本身上直接修改,后期也可以修改配置文件达到相同的目的 · 修改配 ...

随机推荐

  1. 洛谷P1274-魔术数字游戏

    Problem 洛谷P1274-魔术数字游戏 Accept: 118    Submit: 243Time Limit: 1000 mSec    Memory Limit : 128MB Probl ...

  2. [SHOI2015]自动刷题机

    嘟嘟嘟 这题就比较水了,毕竟只评了个蓝. 想一下发现满足单调性,所以可以二分找最大值. 但是最小值怎么办?刚开始我很zz的以为只要把判断条件从大于等于改成小于等于就行了,后来发现根本不对. 想了想因为 ...

  3. jenkins使用4----git maven工具连接

    搭建完git服务器 将jenkins服务器的的公钥传到git服务器的/home/git/.ssh的authorized_keys文件下 ssh端口2994 创建工程 配置完maven发现创建项目没有m ...

  4. ORA-00600: internal error code, arguments: [2662]

    转自 http://www.eygle.com/archives/2005/12/oracle_diagnostics_howto_deal_2662_error.html 在ORA-00600 22 ...

  5. Python排序算法——插入排序

    有趣的事,Python永远不会缺席! 如需转发,请注明出处:小婷儿的python https://www.cnblogs.com/xxtalhr/p/10787464.html 一.插入排序(Inse ...

  6. odoo 基于SQL View视图的model类

    在做odoo的过程中,会涉及到多表的查询, 尤其是做报表的时候这种情况更甚,这样下来会做很多的关联,不是很方便.odoo提供了一种机制,即基于视图的model类.代码地址在这里. 具体过程如下: 1. ...

  7. iOS开发简记(7):网络请求模块

    主流的APP都少不了跟服务器交互,网络请求是少不了的事情. 开源的网络请求库,有很多,比如:AFNetworking.YTKNetwork.PPNetworkHelper.ASIHttpRequest ...

  8. Java 小记 - 时间的处理与探究

    前言 时间的处理与日期的格式转换几乎是所有应用的基础职能之一,几乎所有的语言都会为其提供基础类库.作为曾经 .NET 的重度使用者,赖其优雅的语法,特别是可扩展方法这个神级特性的存在,我几乎没有特意关 ...

  9. 六、Xadmin忘记密码

    1.如果用的是django自带的User模块,忘记了超级用户的密码,可以通过以下方法找回密码: 终端进入项目根目录,然后输入如下命令: python manage.py shell 然后在python ...

  10. Centos7 下SVN迁移

    SVN迁移需要做如下操作: 1. 将原来的Repository导出 . #svnadmin dump 原有repos的目录路径 > dumpfile (不同服务器安装目录不同,根据具体情况调整) ...