一起谈.NET技术,三种属性操作性能比较:PropertyInfo + Expression Tree + Delegate.CreateDelegate

  在《上篇》中,我比较了三种属性操作的性能:直接操作,单纯通过PropertyInfo反射和IL Emit。本篇继续讨论这个话题,我们再引入另外两种额外的属性操作方式:Expression Tree(这和IL Emit基本一致)和通过Delegate的静态方法CreateDelegate创建相应的委托进行属性的赋值和取值。[源代码从这里下载]

目录
一、定义测试相关的接口、类型和委托
二、通过Expression Tree的方式创建用于属性操作的委托
三、编写属性赋值操作测试方法
四、编写属性取值操作测试方法
五、执行测试程序,查看测试结果
六、如果在Expression Tree中避免类型转换呢?

  一、定义测试相关的接口、类型和委托

  我首先定义了一个Bar类型和IFoo接口,该接口中仅仅包含一个类型和名称为Bar的可读写属性。Foo1、Foo2和Foo3均实现接口IFoo,这些接口和类型定义如下:


public class Bar{ }
public interface IFoo
{
Bar Bar { get; set; }
}
public class Foo1 : IFoo
{
public Bar Bar { get; set; }
}
public class Foo2 : IFoo
{
public Bar Bar { get; set; }
}
public class Foo3 : IFoo
{
public Bar Bar { get; set; }
}

  然后定义如下两个委托:GetPropertyValue和SetPropertyValue。如它们的名称所表示的那些,它们分别表示属性取值和赋值操作:


public delegate Bar GetPropertyValue();
public delegate void SetPropertyValue(Bar bar);

  二、通过Expression Tree的方式创建用于属性操作的委托

  接下来我们编写Expression Tree的方式完成属性赋值和取值的操作,它们实现在如下两个静态方法中:CreateGetPropertyValueFunc和CreateSetPropertyValueAction。下面是CreateGetPropertyValueFunc的定义,它返回的是一个Func<object.object>委托:


public static Func<object, object> CreateGetPropertyValueFunc()
{
var property = typeof(IFoo).GetProperty("Bar");
var target = Expression.Parameter(typeof(object));
var castTarget = Expression.Convert(target, typeof(IFoo));
var getPropertyValue = Expression.Property(castTarget, property);
var castPropertyvalue = Expression.Convert(getPropertyValue, typeof(object));
return Expression.Lambda<Func<object, object>>(castPropertyvalue , target).Compile();
}

  下面是CreateSetPropertyValueAction方法,返回一个Action<object.object>委托:


public static Action<object, object> CreateSetPropertyValueAction()
{
var property = typeof(IFoo).GetProperty("Bar");
var target = Expression.Parameter(typeof(object));
var propertyValue = Expression.Parameter(typeof(object));
var castTarget = Expression.Convert(target, typeof(IFoo));
var castPropertyValue = Expression.Convert(propertyValue, property.PropertyType);
var setPropertyValue = Expression.Call(castTarget, property.GetSetMethod(), castPropertyValue);
return Expression.Lambda<Action<object, object>>(setPropertyValue, target, propertyValue).Compile();
}

  三、编写属性赋值操作测试方法

  接下来我们编写程序测试三种不同的属性赋值操作分别具有怎样的性能,所有的测试代码定义在如下TestSetPropertyValue静态方法中。该方法参数表示进行属性赋值操作迭代的次数,每次迭代分别对Foo1、Foo2和Foo3三个对象的Bar属性进行赋值。最后打印出三种赋值操作分别的耗时,时间单位为毫秒。


public static void TestSetPropertyValue(int times)
{
var foo1 = new Foo1();
var foo2 = new Foo2();
var foo3 = new Foo3();
var bar = new Bar();
var property = typeof(IFoo).GetProperty("Bar");
var setAction = CreateSetPropertyValueAction();
var setDelegate1 = CreateSetPropertyValueDelegate(foo1);
var setDelegate2 = CreateSetPropertyValueDelegate(foo2);
var setDelegate3 = CreateSetPropertyValueDelegate(foo3);

var stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < times; i++)
{
property.SetValue(foo1, bar,null);
property.SetValue(foo2, bar, null);
property.SetValue(foo3, bar, null);
}
var duration1 = stopwatch.ElapsedMilliseconds;

stopwatch.Restart();
for (int i = 0; i < times; i++)
{
setAction(foo1, bar);
setAction(foo2, bar);
setAction(foo3, bar);
}
var duration2 = stopwatch.ElapsedMilliseconds;

stopwatch.Restart();
for (int i = 0; i < times; i++)
{
setDelegate1(bar);
setDelegate2(bar);
setDelegate3(bar);
}
var duration3 = stopwatch.ElapsedMilliseconds;
Console.WriteLine("{0, -15}{1,-15}{2,-15}{3,-15}", times, duration1, duration2, duration3);
}

  四、编写属性取值操作测试方法

  属性取值操作的测试方法TestGetPropertyValue与TestSetPropertyValue结构一样。先实例化三个IFoo对象(类型分别分Foo1、Foo2和Foo3),并初始化了它们的Bar属性。然后按照三种不同的方式获取该属性值,并打印出它们各自的耗时。


public static void TestGetPropertyValue(int times)
{
var foo1 = new Foo1 { Bar = new Bar() };
var foo2 = new Foo2 { Bar = new Bar() };
var foo3 = new Foo3 { Bar = new Bar() };

var property = typeof(IFoo).GetProperty("Bar");
var getFunc = CreateGetPropertyValueFunc();
var getDelegate1 = CreateGetPropertyValueDelegate(foo1);
var getDelegate2 = CreateGetPropertyValueDelegate(foo2);
var getDelegate3 = CreateGetPropertyValueDelegate(foo3);

var stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < times; i++)
{
var bar1 = property.GetValue(foo1, null);
var bar2 = property.GetValue(foo2, null);
var bar3 = property.GetValue(foo3, null);
}
var duration1 = stopwatch.ElapsedMilliseconds;

stopwatch.Restart();
for (int i = 0; i < times; i++)
{
var bar1 = getFunc(foo1);
var bar2 = getFunc(foo2);
var bar3 = getFunc(foo3);
}
var duration2 = stopwatch.ElapsedMilliseconds;

stopwatch.Restart();
for (int i = 0; i < times; i++)
{
var bar1 = getDelegate1();
var bar2 = getDelegate2();
var bar3 = getDelegate3();
}
var duration3 = stopwatch.ElapsedMilliseconds;

Console.WriteLine("{0, -15}{1,-15}{2,-15}{3,-15}", times, duration1, duration2, duration3);
}

  五、执行测试程序,查看测试结果

  我们直接通过一个Console应用来测试,在Main()方法中编写了如下的测试程序。先三次调用TestSetPropertyValue方法测试属性赋值操作,传入表示迭代次数的参数分别为10000(一万)、100000(十万)和1000000(一百万)。然后按照相同的方式调用TestGetPropertyValue测试属性取值操作。


static void Main()
{
Console.WriteLine("{0, -15}{1,-15}{2,-15}{3,-15}", "Times", "Reflection", "Expression", "Delegate");
TestSetPropertyValue(10000);
TestSetPropertyValue(100000);
TestSetPropertyValue(1000000);

Console.WriteLine();

TestGetPropertyValue(10000);
TestGetPropertyValue(100000);
TestGetPropertyValue(1000000);
}

  从下面的输出结果来看,不论是属性的赋值还是取值,单纯通过PropertyInfo的方式所耗用的时间都比其它两种形式要长的多。至于其它两种(Expression Tree和通过Delegate.CreateDelegate创建委托)来说,后者又比前者有明显的优势。


Times Reflection Expression Delegate
10000 109 2 0
100000 992 21 3
1000000 9872 210 37

10000 80 2 0
100000 800 23 2
1000000 8007 239 28

  六、如果在Expression Tree中避免类型转换呢?

  当我们调用Delegate的静态方法CreateDelegate是,需要指定具体的委托类型。对于属性的操作来说,属性类型需要与指定的委托类型相匹配,所以这就避免了类型转化这个步骤。但是对于Expression Tree的属性操作来说,由于返回的类型是Func<object,object>和Action<object,object>,需要对目标对象和属性值进行两次类型转换。如果将类型转换这个步骤从Expression Tree中移掉,两者的性能是否一致呢?

  我们不妨来试试看。现在我们修改CreateGetPropertyValueFunc和CreateSetPropertyValueAction这两个静态方法,让它们直接返回Func<IFoo,Bar>和Action<IFoo, Bar>,并去掉Expression.Convert语句。两个方法现在的定义如下:


public static Func<IFoo, Bar> CreateGetPropertyValueFunc()
{
var property = typeof(IFoo).GetProperty("Bar");
var target = Expression.Parameter(typeof(IFoo));
var getPropertyValue = Expression.Property(target, property);
return Expression.Lambda<Func<IFoo, Bar>>(getPropertyValue, target).Compile();
}
public static Action<IFoo, Bar> CreateSetPropertyValueAction()
{
var property = typeof(IFoo).GetProperty("Bar");
var target = Expression.Parameter(typeof(IFoo));
var propertyValue = Expression.Parameter(typeof(Bar));
var setPropertyValue = Expression.Call(target, property.GetSetMethod(), propertyValue);
return Expression.Lambda<Action<IFoo, Bar>>(setPropertyValue, target, propertyValue).Compile();
}

  这种情况下,再次运行我们的测试程序,你会得到如下的输出结果。从中我们不难看出,通过上面的修改,Expression Tree形式的操作在性能上得到了一定的提升,但是和第三种依然有一定的差距。


Times Reflection Expression Delegate
10000 107 1 0
100000 982 15 3
1000000 9802 157 37

10000 79 1 0
100000 789 18 2
1000000 7901 178 28

时间: 2024-10-22 20:40:26

一起谈.NET技术,三种属性操作性能比较:PropertyInfo + Expression Tree + Delegate.CreateDelegate的相关文章

三种属性操作性能比较:PropertyInfo + Expression Tree + Delegate.CreateDelegate

在<上篇>中,我比较了三种属性操作的性能:直接操作,单纯通过PropertyInfo反射和IL Emit.本篇继续讨论这个话题,我们再引入另外两种额外的属性操作方式:Expression Tree(这和IL Emit基本一致)和通过Delegate的静态方法CreateDelegate创建相应的委托进行属性的赋值和取值.[源代码从这里下载] 目录 一.定义测试相关的接口.类型和委托 二.通过Expression Tree的方式创建用于属性操作的委托 三.编写属性赋值操作测试方法 四.编写属性取

Delphi2010中DataSnap高级技术(7)—TDSServerClass中Lifecycle生命周期三种属性说明

Lifecycle 三种属性: Session.Invocation.Server 这三种属性都用在什么情况,有什么要注意的事项,Delphi2010中罕有说明. 如果乱用这三种属性,你的服务程序有可能崩溃,数据混乱,内存占用大,效率低等问题! 下面我对这三种属性的使用环境逐一介绍: 1. Session 说明:这是delphi2010中默认属性,也是delphi推荐设置.Session会为每个来自客户端的链接,建立一个线程来实例化.实例化是什么概念呢?就是这个线程把所有你将要用到的类.函数等等

浅谈合理利用三种页面类型 提高站点的收录

  站内的页面类型已经演化为三种,分别是静态页面.动态页面和伪静态页面.有点优化基础的人都了解,静态页面是对搜索引擎最友好的页面类型,因此这一类的页面也是最容易被搜索引擎收录的.事实真的是如此吗?笔者认为答案未必,笔者认为效果最好的就是三者合理利用,因为这三者免不了都有各自的优缺点.只有在对的地方使用他们才可以发挥出最好的效果. 一:静态页面 静态页面一直是被认为最好优化的页面,却是也是如此,相对与动态页面,静态页面更对搜索引擎更加的友好,但是我们也不能忽视一个大的问题,那就是静态页面所占用的空

浅谈iOS中三种生成随机数方法_IOS

ios 有如下三种随机数方法: //第一种 srand((unsigned)time(0)); //不加这句每次产生的随机数不变 int i = rand() % 5; //第二种 srandom(time(0)); int i = random() % 5; //第三种 int i = arc4random() % 5 ; 注: ① rand()和random()实际并不是一个真正的伪随机数发生器,在使用之前需要先初始化随机种子,否则每次生成的随机数一样.       ② arc4random

一起谈.NET技术,.NET Framework源码研究系列之---Delegate

前言 曾几何时能看到微软产品的源码简直是天方夜谭,不过现在这却成了现实,微软终于对外开放了它的产品的源代码.抛去开源运动与微软之间的世代情仇,抛去微软这一做法的初衷,这总归是件好事,能够让我们拨开云雾,一窥优秀产品的秘密. 前两天看到有位仁兄在随笔中的留言,说他以为".NET中的设计模式"是在讲.NET Framework与设计模式的关系,其实不是,不过这也让我想起来自己确实研究过.NET Framework的源码,于是就找打算找时间把自己的心得体会拿出来和大家一起分享. 今天就先从最

一起谈.NET技术,晚绑定场景下对象属性赋值和取值可以不需要PropertyInfo

在<一句代码实现批量数据绑定>中,我通过界面控件ID与作为数据源的实体属性名之间的映射实现了批量数据绑定.由于里面频繁涉及对属性的反射--通过反射从实体对象中获取某个属性值:通过反射为控件的某个属性赋值,所以这不是一种高效的操作方式.为了提升性能,我通过IL Emit的方式创建了一个PropertyAccessor组件,以实现高效的属性操作.如果你看了我在文中给出的三种属性操作性能的测试结果,相信会对PropertyAccessor的作用有深刻的印象.[源代码从这里下载] 目录: 一.Prop

一起谈.NET技术,关于Expression Tree和IL Emit的所谓的&amp;quot;性能差别&amp;quot;

昨天写了<三种属性操作性能比较>,有个网友写信问我一个问题:从性能上看,Expression Tree和IL Emit孰优孰劣?虽然我在回信中作了简单的回答,但不知道这个网友是否懂我的意思.反正今天呆在家里也没事儿,干脆再就这个话题再写一篇文章. 目录: 一.Expression Tree和IL Emit并不存在所谓的性能差异 二.属性赋值操作的两种写法 三.属性取值操作的两种写法 四.两种写法对应的IL 一.Expression Tree和IL Emit并不存在所谓的性能差异 Express

一起谈.NET技术,提供第三种代码生成方式——通过自定义BuildProvider为ASP.NET提供代码生成

之前写了一些关于代码生成的文章,提供了两种不同方式的代码生成解决方案,即CodeDOM+Custom Tool和T4.对于ASP.NET应用,你还有第三种选择--自定义BuildProvider.[文中涉及的源代码从这里下载] 目录 一.BuildProvider是什么? 二.将XML表示的消息转换成VB.NET或者C#代码 三.将XML转换成CodeDOM 四.自定义BuildProvider 五.BuildProvider的应用 一.BuildProvider是什么? 对于ASP.NET应用

一起谈.NET技术,Asp.net mvc 2中使用Ajax的三种方式

     在Asp.net MVC中,我们能非常方便的使用Ajax.这篇文章将介绍三种Ajax使用的方式,分别为原始的Ajax调用.Jquery.Ajax Helper.分别采用这三种方式结合asp.net mvc去实现一个史上最简单的留言板.     首先看一下原始的Ajax的调用的:      定义CommentController,代码如下: public class CommentController : Controller{private IList<string> _commen