閱讀866 返回首頁    go 技術社區[雲棲]


三種屬性操作性能比較: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,這些接口和類型定義如下:

   1: public class Bar{ }
   2: public interface IFoo
   3: {
   4:     Bar Bar { get; set; }
   5: }
   6: public class Foo1 : IFoo
   7: {
   8:     public Bar Bar { get; set; }
   9: }
  10: public class Foo2 : IFoo
  11: {
  12:     public Bar Bar { get; set; }
  13: }
  14: public class Foo3 : IFoo
  15: {
  16:     public Bar Bar { get; set; }
  17: }

然後定義如下兩個委托:GetPropertyValue和SetPropertyValue。如它們的名稱所表示的那些,它們分別表示屬性取值和賦值操作:

   1: public delegate Bar GetPropertyValue();
   2: public delegate void SetPropertyValue(Bar bar);

接下來我們編寫Expression Tree的方式完成屬性賦值和取值的操作,它們實現在如下兩個靜態方法中:CreateGetPropertyValueFunc和CreateSetPropertyValueAction。下麵是CreateGetPropertyValueFunc的定義,它返回的是一個Func<object.object>委托:

   1: public static Func<object, object> CreateGetPropertyValueFunc()
   2: {
   3:     var property            = typeof(IFoo).GetProperty("Bar");
   4:     var target              = Expression.Parameter(typeof(object));
   5:     var castTarget          = Expression.Convert(target, typeof(IFoo));
   6:     var getPropertyValue    = Expression.Property(castTarget, property);
   7:     var castPropertyvalue   = Expression.Convert(getPropertyValue, typeof(object));
   8:     return Expression.Lambda<Func<object, object>>(castPropertyvalue , target).Compile();
   9: }

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

   1: public static Action<object, object> CreateSetPropertyValueAction()
   2: {
   3:     var property            = typeof(IFoo).GetProperty("Bar");
   4:     var target              = Expression.Parameter(typeof(object));
   5:     var propertyValue       = Expression.Parameter(typeof(object));
   6:     var castTarget          = Expression.Convert(target, typeof(IFoo));
   7:     var castPropertyValue   = Expression.Convert(propertyValue, property.PropertyType);
   8:     var setPropertyValue    = Expression.Call(castTarget, property.GetSetMethod(), castPropertyValue);
   9:     return Expression.Lambda<Action<object, object>>(setPropertyValue, target, propertyValue).Compile();
  10: }

接下來我們編寫程序測試三種不同的屬性賦值操作分別具有怎樣的性能,所有的測試代碼定義在如下TestSetPropertyValue靜態方法中。該方法參數表示進行屬性賦值操作迭代的次數,每次迭代分別對Foo1、Foo2和Foo3三個對象的Bar屬性進行賦值。最後打印出三種賦值操作分別的耗時,時間單位為毫秒。

   1: public static void TestSetPropertyValue(int times)
   2: {
   3:     var foo1            = new Foo1();
   4:     var foo2            = new Foo2();
   5:     var foo3            = new Foo3();
   6:     var bar             = new Bar();
   7:     var property        = typeof(IFoo).GetProperty("Bar");
   8:     var setAction       = CreateSetPropertyValueAction();
   9:     var setDelegate1    = CreateSetPropertyValueDelegate(foo1);
  10:     var setDelegate2    = CreateSetPropertyValueDelegate(foo2);
  11:     var setDelegate3    = CreateSetPropertyValueDelegate(foo3);
  12:  
  13:     var stopwatch = new Stopwatch();
  14:     stopwatch.Start();
  15:     for (int i = 0; i < times; i++)
  16:     {
  17:         property.SetValue(foo1, bar,null);
  18:         property.SetValue(foo2, bar, null);
  19:         property.SetValue(foo3, bar, null);
  20:     }
  21:     var duration1 = stopwatch.ElapsedMilliseconds;
  22:  
  23:     stopwatch.Restart();
  24:     for (int i = 0; i < times; i++)
  25:     {
  26:         setAction(foo1, bar);
  27:         setAction(foo2, bar);
  28:         setAction(foo3, bar);
  29:     }
  30:     var duration2 = stopwatch.ElapsedMilliseconds;
  31:  
  32:     stopwatch.Restart();
  33:     for (int i = 0; i < times; i++)
  34:     {
  35:         setDelegate1(bar);
  36:         setDelegate2(bar);
  37:         setDelegate3(bar);
  38:     }
  39:     var duration3 = stopwatch.ElapsedMilliseconds;            
  40:     Console.WriteLine("{0, -15}{1,-15}{2,-15}{3,-15}", times, duration1, duration2, duration3);
  41: }

屬性取值操作的測試方法TestGetPropertyValue與TestSetPropertyValue結構一樣。先實例化三個IFoo對象(類型分別分Foo1、Foo2和Foo3),並初始化了它們的Bar屬性。然後按照三種不同的方式獲取該屬性值,並打印出它們各自的耗時。

   1: public static void TestGetPropertyValue(int times)
   2: {
   3:     var foo1            = new Foo1 { Bar = new Bar() };
   4:     var foo2            = new Foo2 { Bar = new Bar() };
   5:     var foo3            = new Foo3 { Bar = new Bar() };
   6:  
   7:     var property        = typeof(IFoo).GetProperty("Bar");
   8:     var getFunc         = CreateGetPropertyValueFunc();
   9:     var getDelegate1    = CreateGetPropertyValueDelegate(foo1);
  10:     var getDelegate2    = CreateGetPropertyValueDelegate(foo2);
  11:     var getDelegate3    = CreateGetPropertyValueDelegate(foo3);
  12:  
  13:     var stopwatch = new Stopwatch();
  14:     stopwatch.Start();
  15:     for (int i = 0; i < times; i++)
  16:     {
  17:         var bar1 = property.GetValue(foo1, null);
  18:         var bar2 = property.GetValue(foo2, null);
  19:         var bar3 = property.GetValue(foo3, null);
  20:     }
  21:     var duration1 = stopwatch.ElapsedMilliseconds;
  22:  
  23:     stopwatch.Restart();
  24:     for (int i = 0; i < times; i++)
  25:     {
  26:         var bar1 = getFunc(foo1);
  27:         var bar2 = getFunc(foo2);
  28:         var bar3 = getFunc(foo3);
  29:     }
  30:     var duration2 = stopwatch.ElapsedMilliseconds;
  31:  
  32:     stopwatch.Restart();
  33:     for (int i = 0; i < times; i++)
  34:     {
  35:         var bar1 = getDelegate1();
  36:         var bar2 = getDelegate2();
  37:         var bar3 = getDelegate3();
  38:     }
  39:     var duration3 = stopwatch.ElapsedMilliseconds;
  40:  
  41:     Console.WriteLine("{0, -15}{1,-15}{2,-15}{3,-15}", times, duration1, duration2, duration3);
  42: }

我們直接通過一個Console應用來測試,在Main()方法中編寫了如下的測試程序。先三次調用TestSetPropertyValue方法測試屬性賦值操作,傳入表示迭代次數的參數分別為10000(一萬)、100000(十萬)和1000000(一百萬)。然後按照相同的方式調用TestGetPropertyValue測試屬性取值操作。

   1: static void Main()
   2: {
   3:     Console.WriteLine("{0, -15}{1,-15}{2,-15}{3,-15}", "Times", "Reflection", "Expression", "Delegate");
   4:     TestSetPropertyValue(10000);
   5:     TestSetPropertyValue(100000);
   6:     TestSetPropertyValue(1000000);
   7:  
   8:     Console.WriteLine();
   9:  
  10:     TestGetPropertyValue(10000);
  11:     TestGetPropertyValue(100000);
  12:     TestGetPropertyValue(1000000);
  13: }

從下麵的輸出結果來看,不論是屬性的賦值還是取值,單純通過PropertyInfo的方式所耗用的時間都比其它兩種形式要長的多。至於其它兩種(Expression Tree和通過Delegate.CreateDelegate創建委托)來說,後者又比前者有明顯的優勢。

   1: Times          Reflection     Expression     Delegate
   2: 10000          109            2              0
   3: 100000         992            21             3
   4: 1000000        9872           210            37
   5:  
   6: 10000          80             2              0
   7: 100000         800            23             2
   8: 1000000        8007           239            28

當我們調用Delegate的靜態方法CreateDelegate是,需要指定具體的委托類型。對於屬性的操作來說,屬性類型需要與指定的委托類型相匹配,所以這就避免了類型轉化這個步驟。但是對於Expression Tree的屬性操作來說,由於返回的類型是Func<object,object>和Action<object,object>,需要對目標對象和屬性值進行兩次類型轉換。如果將類型轉換這個步驟從Expression Tree中移掉,兩者的性能是否一致呢?

我們不妨來試試看。現在我們修改CreateGetPropertyValueFunc和CreateSetPropertyValueAction這兩個靜態方法,讓它們直接返回Func<IFoo,Bar>和Action<IFoo, Bar>,並去掉Expression.Convert語句。兩個方法現在的定義如下:

   1: public static Func<IFoo, Bar> CreateGetPropertyValueFunc()
   2: {
   3:     var property            = typeof(IFoo).GetProperty("Bar");
   4:     var target              = Expression.Parameter(typeof(IFoo));
   5:     var getPropertyValue    = Expression.Property(target, property);
   6:     return Expression.Lambda<Func<IFoo, Bar>>(getPropertyValue, target).Compile();
   7: }
   8: public static Action<IFoo, Bar> CreateSetPropertyValueAction()
   9: {
  10:     var property            = typeof(IFoo).GetProperty("Bar");
  11:     var target              = Expression.Parameter(typeof(IFoo));
  12:     var propertyValue       = Expression.Parameter(typeof(Bar));
  13:     var setPropertyValue    = Expression.Call(target, property.GetSetMethod(), propertyValue);
  14:     return Expression.Lambda<Action<IFoo, Bar>>(setPropertyValue, target, propertyValue).Compile();
  15: }

在這種情況下,再次運行我們的測試程序,你會得到如下的輸出結果。從中我們不難看出,通過上麵的修改,Expression Tree形式的操作在性能上得到了一定的提升,但是和第三種依然有一定的差距。

   1: Times          Reflection     Expression     Delegate
   2: 10000          107            1              0
   3: 100000         982            15             3
   4: 1000000        9802           157            37
   5:  
   6: 10000          79             1              0
   7: 100000         789            18             2
   8: 1000000        7901           178            28

晚綁定場景下對象屬性賦值和取值可以不需要PropertyInfo
三種屬性操作性能比較:PropertyInfo + Expression Tree + Delegate.CreateDelegate
關於Expression Tree和IL Emit的所謂的"性能差別"


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-27 11:33:57

  上一篇:go  電腦硬盤文件不小心刪除了怎麼恢複免費恢複不需要注冊碼
  下一篇:go  小編給各位小夥伴分享一款OSS費用統計工具