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


晚綁定場景下對象屬性賦值和取值可以不需要PropertyInfo

在《一句代碼實現批量數據綁定》中,我通過界麵控件ID與作為數據源的實體屬性名之間的映射實現了批量數據綁定。由於裏麵頻繁涉及對屬性的反射——通過反射從實體對象中獲取某個屬性值;通過反射為控件的某個屬性賦值,所以這不是一種高效的操作方式。為了提升性能,我通過IL Emit的方式創建了一個PropertyAccessor組件,以實現高效的屬性操作。如果你看了我在文中給出的三種屬性操作性能的測試結果,相信會對PropertyAccessor的作用有深刻的印象。[源代碼從這裏下載]

目錄:
一、PropertyAccessor與PropertyAccessor<T>的API定義
二、如何通過PropertyAccessor獲取屬性值和為屬性賦值
三、Set和Get的實現
四、比較三種屬性操作的性能
五、PropertyAccessor的ExpressionTree版本

我們照例從編程——即如何使用PropertyAccessor進行屬性操作(獲取屬性值/為屬性賦值)講起,所有先來看看PropertyAccessor提供了哪些API功我們調用。從下麵的代碼片斷我們可以看到,PropertyAccessor得構造函數接受兩個參數:目標對象的類型和屬性名稱,然後通過Get獲取目標對象相應屬性的值,通過Set方法為目標對象的屬性進行賦值。此外,PropertyAccessor還提供了兩個對應的Get/Set靜態方法通過指定具體的目標對象和屬性名稱實現相同的操作。

   1: public class PropertyAccessor
   2: {
   3:     public PropertyAccessor(Type targetType, string propertyName);    
   4:  
   5:     public object Get(object obj);
   6:     public void Set(object obj, object value);
   7:  
   8:     public static object Get(object obj, string propertyName);
   9:     public static void Set(object obj, string propertyName, object value);
  10:     //Others...
  11: }

如果預先知道了目標對象的類型,可能使用泛型的PropertyAccessor<T>會使操作更加方便。PropertyAccessor<T>繼承自PropertyAccessor,定義如下:

   1: public class PropertyAccessor<T> : PropertyAccessor
   2: {
   3:     public PropertyAccessor(string propertyName);
   4:  
   5:     public static object Get(T obj, string propertyName);
   6:     public static void Set(T obj, string propertyName, object value);
   7: }

現在我們來演示如何通PropertyAccessor<T>來對目標對象的屬性賦值,以及如何或者目標對象相應屬性的值。現在我們定義如下一個實體類型:Contact。

   1: public class Contact
   2: {
   3:     public string       FirstName { get; set; }
   4:     public string       LastName { get; set; }
   5:     public string       Gender { get; set; }
   6:     public int?         Age { get; set; }
   7:     public DateTime?    Birthday { get; set; }
   8: }

然後我們在一個Console應用的Main方法中編寫如下一段代碼。在這段代碼中,我創建了一個Contact對象,然後通過調用PropertyAccessor<Contact>類型的靜態方法Set為該對象的各個屬性進行複製。然後將各個屬性值按照一定的格式打印出來,而獲取屬性值是通過調用靜態方法Get完成的。

   1: static void Main(string[] args)
   2: {
   3:     var contact = new Contact();
   4:  
   5:     PropertyAccessor<Contact>.Set(contact, "FirstName", "Jiang");
   6:     PropertyAccessor<Contact>.Set(contact, "LastName", "Jin Nan");
   7:     PropertyAccessor<Contact>.Set(contact, "Gender", "Male");
   8:     PropertyAccessor<Contact>.Set(contact, "Age", 30);
   9:     PropertyAccessor<Contact>.Set(contact, "Birthday", new DateTime(1981, 8, 24));
  10:  
  11:     Console.WriteLine("Contact({0} {1})\n\tGender\t:{2}\n\tAge\t:{3}\n\tBirth\t:{4}",
  12:         PropertyAccessor<Contact>.Get(contact, "FirstName"),
  13:         PropertyAccessor<Contact>.Get(contact, "LastName"),
  14:         PropertyAccessor<Contact>.Get(contact, "Gender"),
  15:         PropertyAccessor<Contact>.Get(contact, "Age"),
  16:         PropertyAccessor<Contact>.Get(contact, "Birthday"));
  17: }

輸出結果:

   1: Contact(Jiang Jin Nan)
   2:         Gender  :Male
   3:         Age     :30
   4:         Birth   :8/24/1981 12:00:00 AM

雖然PropertyAccessor是一個很小的組件,但也不太可能將所有的代碼列出來。在這裏,我隻是隻能將核心部分作一下簡單介紹,如果你想了解整個PropertyAccessor的實現,可以下載源代碼。PropertyAccessor的兩個核心的方法就是Get和Set。而在內部,它們對應著兩個核心的方法:CreateGetFunction和CreateSetAction,它們利用IL Emit。下麵是CreateGetFunction的實現:創建一個DynamicMethod對象,通過IL Emit調用屬性的Getter方法,並將結果返回。最後通過DynamicMethod的CreateDelegate方法創建一個Func<object,object>委托對象並在本地緩存起來,供或許的獲取屬性值操作之用。

   1: private Func<object, object> CreateGetFunction()
   2: {
   3:      //...
   4:     DynamicMethod method = new DynamicMethod("GetValue", typeof(object), new Type[] { typeof(object) });
   5:     ILGenerator ilGenerator = method.GetILGenerator();
   6:     ilGenerator.DeclareLocal(typeof(object));
   7:     ilGenerator.Emit(OpCodes.Ldarg_0);
   8:     ilGenerator.Emit(OpCodes.Castclass, this.TargetType);
   9:     ilGenerator.EmitCall(OpCodes.Call, this.GetMethod, null);
  10:     if (this.GetMethod.ReturnType.IsValueType)
  11:     {
  12:         ilGenerator.Emit(OpCodes.Box, this.GetMethod.ReturnType);
  13:     }
  14:     ilGenerator.Emit(OpCodes.Stloc_0);
  15:     ilGenerator.Emit(OpCodes.Ldloc_0);
  16:     ilGenerator.Emit(OpCodes.Ret);
  17:  
  18:     method.DefineParameter(1, ParameterAttributes.In, "value");
  19:     return (Func<object, object>)method.CreateDelegate(typeof(Func<object, object>));
  20: }

與CreateGetFunction類似,CreateSetAction同樣創建一個DynamicMethod對象,通過IL Emit的方式調用屬性的Setter方法。最後通過DynamicMethod的CreateDelegate方法創建一個Action<object,object>委托對象並在本地緩存起來,供後續的屬性賦值操作之用。

   1: private Action<object, object> CreateSetAction()
   2: {
   3:     //...
   4:     DynamicMethod method = new DynamicMethod("SetValue", null, new Type[] { typeof(object), typeof(object) });
   5:     ILGenerator ilGenerator = method.GetILGenerator();
   6:     Type paramType = this.SetMethod.GetParameters()[0].ParameterType;
   7:     ilGenerator.DeclareLocal(paramType);
   8:     ilGenerator.Emit(OpCodes.Ldarg_0);
   9:     ilGenerator.Emit(OpCodes.Castclass, this.TargetType);
  10:     ilGenerator.Emit(OpCodes.Ldarg_1);
  11:     if (paramType.IsValueType)
  12:     {
  13:         ilGenerator.Emit(OpCodes.Unbox, paramType);
  14:         if (valueTpyeOpCodes.ContainsKey(paramType))
  15:         {
  16:             OpCode load = (OpCode)valueTpyeOpCodes[paramType];
  17:             ilGenerator.Emit(load);
  18:         }
  19:         else
  20:         {
  21:             ilGenerator.Emit(OpCodes.Ldobj, paramType);
  22:         }
  23:     }
  24:     else
  25:     {
  26:         ilGenerator.Emit(OpCodes.Castclass, paramType); 
  27:     }
  28:  
  29:     ilGenerator.EmitCall(OpCodes.Callvirt, this.SetMethod, null);
  30:     ilGenerator.Emit(OpCodes.Ret);
  31:  
  32:     method.DefineParameter(1, ParameterAttributes.In, "obj");
  33:     method.DefineParameter(2, ParameterAttributes.In, "value");
  34:     return (Action<object, object>)method.CreateDelegate(typeof(Action<object, object>));
  35: }

我想大家最關心的還是“性能”的問題,現在我們就來編寫一個性能測試的程序。在這個程序中我們比較三種典型的屬性操作耗費的時間:直接通過屬性賦值(或者取值)、通過IL Emit(即PropertyAccessor)和PropertyInfo對屬性賦值(或者取值)。我們定義兩個簡單的類型Foo和Bar,Foo中定義一個類型和名稱為Bar的可讀寫的屬性。

   1: public class Foo
   2: {
   3:     public Bar Bar { get; set; }
   4: }
   5: public class Bar
   6: { }

下麵是用於比較三種屬性複製操作的測試程序SetTest,方法參數為複製操作的次數,最後將三種屬性賦值操作的總時間(單位毫秒)分別打印出來。

   1: public static void SetTest(int times)
   2: {
   3:     Foo foo = new Foo();
   4:     Bar bar = new Bar();
   5:     Stopwatch stopwatch = new Stopwatch();
   6:     PropertyAccessor<Foo> propertyAccessor = new PropertyAccessor<Foo>("Bar");
   7:     PropertyInfo propertyInfo = typeof(Foo).GetProperty("Bar");
   8:     stopwatch.Start();
   9:     for (int i = 0; i < times; i++)
  10:     {
  11:         foo.Bar = bar;
  12:     }
  13:     long duration1 = stopwatch.ElapsedMilliseconds;
  14:  
  15:     stopwatch.Restart();
  16:     for (int i = 0; i < times; i++)
  17:     {
  18:         propertyAccessor.Set(foo, bar);
  19:     }
  20:     long duration2 = stopwatch.ElapsedMilliseconds;
  21:  
  22:     stopwatch.Restart();
  23:     for (int i = 0; i < times; i++)
  24:     {
  25:         propertyInfo.SetValue(foo, bar, null);
  26:     }
  27:     long duration3 = stopwatch.ElapsedMilliseconds;
  28:     Console.WriteLine("{0,-10}{1,-10}{2,-10}{3,-10}", times, duration1, duration2, duration3);
  29: }

下麵是下麵是用於比較三種或者屬性值操作的測試程序GetTest,定義形式和上麵一樣:

   1: public static void GetTest(int times)
   2: {
   3:     Foo foo = new Foo { Bar = new Bar() };
   4:     Stopwatch stopwatch = new Stopwatch();
   5:     PropertyAccessor<Foo> propertyAccessor = new PropertyAccessor<Foo>("Bar");
   6:     PropertyInfo propertyInfo = typeof(Foo).GetProperty("Bar");
   7:     stopwatch.Start();
   8:     for (int i = 0; i < times; i++)
   9:     {
  10:         var bar = foo.Bar;
  11:     }
  12:     long duration1 = stopwatch.ElapsedMilliseconds;
  13:  
  14:     stopwatch.Restart();
  15:     for (int i = 0; i < times; i++)
  16:     {
  17:         var bar = propertyAccessor.Get(foo);
  18:     }
  19:     long duration2 = stopwatch.ElapsedMilliseconds;
  20:  
  21:     stopwatch.Restart();
  22:     for (int i = 0; i < times; i++)
  23:     {
  24:         var bar = propertyInfo.GetValue(foo, null);
  25:     }
  26:     long duration3 = stopwatch.ElapsedMilliseconds;
  27:     Console.WriteLine("{0,-10}{1,-10}{2,-10}{3,-10}", times, duration1, duration2, duration3);
  28: }

然後,我們在Console應用的Main方法中編寫如下的代碼,旨在測試次數分別為100000(十萬)、1000000(一百萬)和10000000(一千萬)下三種不同形式的屬性操作所耗用的時間。

   1: static void Main(string[] args)
   2: {
   3:     Console.WriteLine("{0,-10}{1,-10}{2,-10}{3,-10}", "Times", "General", "IL Emit", "Reflection");
   4:     SetTest(100000);
   5:     SetTest(1000000);
   6:     SetTest(10000000);
   7:  
   8:     Console.WriteLine();
   9:     GetTest(100000);
  10:     GetTest(1000000);
  11:     GetTest(10000000);
  12: }

輸出結果:

   1: Times     General   IL Emit   Reflection
   2: 100000    1         17        204
   3: 1000000   12        110       1918
   4: 10000000  131       1103      18919
   5:  
   6: 100000    1         10        153
   7: 1000000   11        101       1534
   8: 10000000  112       1009      15425

由於我的筆記本已經差不多5年的曆史,性能不是很好,所以更能反映出三種操作類型的性能差異。我們對屬性直接進行賦值和取值是最快的,這一點沒有什麼好說的。我們關心的是,IL Emit的方式和單純使用PropertyInfo進行反射(並且值得一提的是:PropertyInfo之前已經保存起來,並沒有頻繁去創建)的方式這兩者的性能依然有本質的差別。如果你對數字不是敏感,那就看看下麵的曲線圖吧。

image

五、PropertyAccessor的ExpressionTree版本(2011-03-25)

對於很多人來說,IL Emit編程是一件很繁瑣的事。反正我多這比較頭疼,我一般的做法都是將需要的邏輯通過代碼寫出來,編譯之後跟據IL寫Emit代碼。而我們更喜歡采用的則是ExpressionTree,為此我編寫了PropertyAccessor的ExpressionTree版本(你可以從這裏下載)。兩個版本主要的不同還是在於上述兩個方法:CreateGetFunction和CreateSetAction。下麵是兩個方法的定義:

   1: private Func<object, object> CreateGetFunction()
   2: {
   3:     var getMethod = this.Property.GetGetMethod();
   4:     var target = Expression.Parameter(typeof(object), "target");
   5:     var castedTarget = getMethod.IsStatic ? null : Expression.Convert(target, this.TargetType);
   6:     var getProperty = Expression.Property(castedTarget, this.Property);
   7:     var castPropertyValue = Expression.Convert(getProperty, typeof(object));
   8:     return Expression.Lambda<Func<object, object>>(castPropertyValue, target).Compile();
   9: }
  10:  
  11: private Action<object, object> CreateSetAction()
  12: {
  13:     var setMethod = this.Property.GetSetMethod();
  14:     var target = Expression.Parameter(typeof(object), "target");
  15:     var propertyValue = Expression.Parameter(typeof(object), "value");
  16:     var castedTarget = setMethod.IsStatic ? null : Expression.Convert(target, this.TargetType);
  17:     var castedpropertyValue = Expression.Convert(propertyValue, this.PropertyType);
  18:     var propertySet = Expression.Call(castedTarget, setMethod, castedpropertyValue);
  19:     return Expression.Lambda<Action<object, object>>(propertySet, target, propertyValue).Compile();
  20: }

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


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

最後更新:2017-10-27 11:34:04

  上一篇:go  無人化運維離我們有多遠?阿裏智能化運帷平台深度揭秘
  下一篇:go  陌車購車係統開發