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

作者: Artech  來源: 博客園  發布時間: 2011-03-30 13:45  閱讀: 1616 次  推薦: 0   原文鏈接   [收藏]  

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

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

  一、PropertyAccessor與PropertyAccessor<T>的API定義

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

 
public class PropertyAccessor
{

public PropertyAccessor(Type targetType, string propertyName);

public object Get(object obj);
public void Set(object obj, object value);

public static object Get(object obj, string propertyName);
public static void Set(object obj, string propertyName, object value);
//Others...
}

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

 
public class PropertyAccessor<T> : PropertyAccessor
{

public PropertyAccessor(string propertyName);

public static object Get(T obj, string propertyName);
public static void Set(T obj, string propertyName, object value);
}

  二、如何通過PropertyAccessor獲取屬性值和為屬性賦值

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

 
public class Contact
{

public string FirstName { get; set; }
public string LastName { get; set; }
public string Gender { get; set; }
public int? Age { get; set; }
public DateTime? Birthday { get; set; }
}

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

 
static void Main(string[] args)
{
var contact
= new Contact();

PropertyAccessor
<Contact>.Set(contact, "FirstName", "Jiang");
PropertyAccessor
<Contact>.Set(contact, "LastName", "Jin Nan");
PropertyAccessor
<Contact>.Set(contact, "Gender", "Male");
PropertyAccessor
<Contact>.Set(contact, "Age", 30);
PropertyAccessor
<Contact>.Set(contact, "Birthday", new DateTime(1981, 8, 24));

Console.WriteLine(
"Contact({0} {1})\n\tGender\t:{2}\n\tAge\t:{3}\n\tBirth\t:{4}",
PropertyAccessor
<Contact>.Get(contact, "FirstName"),
PropertyAccessor
<Contact>.Get(contact, "LastName"),
PropertyAccessor
<Contact>.Get(contact, "Gender"),
PropertyAccessor
<Contact>.Get(contact, "Age"),
PropertyAccessor
<Contact>.Get(contact, "Birthday"));
}

  輸出結果:

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

  三、Set和Get的實現

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

 
private Func<object, object> CreateGetFunction()
{

//...
DynamicMethod method = new DynamicMethod("GetValue", typeof(object), new Type[] { typeof(object) });
ILGenerator ilGenerator
= method.GetILGenerator();
ilGenerator.DeclareLocal(
typeof(object));
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Castclass,
this.TargetType);
ilGenerator.EmitCall(OpCodes.Call,
this.GetMethod, null);
if (this.GetMethod.ReturnType.IsValueType)
{
ilGenerator.Emit(OpCodes.Box,
this.GetMethod.ReturnType);
}
ilGenerator.Emit(OpCodes.Stloc_0);
ilGenerator.Emit(OpCodes.Ldloc_0);
ilGenerator.Emit(OpCodes.Ret);

method.DefineParameter(
1, ParameterAttributes.In, "value");
return (Func<object, object>)method.CreateDelegate(typeof(Func<object, object>));
}

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

 
private Action<object, object> CreateSetAction()
{

//...
DynamicMethod method = new DynamicMethod("SetValue", null, new Type[] { typeof(object), typeof(object) });
ILGenerator ilGenerator
= method.GetILGenerator();
Type paramType
= this.SetMethod.GetParameters()[0].ParameterType;
ilGenerator.DeclareLocal(paramType);
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Castclass,
this.TargetType);
ilGenerator.Emit(OpCodes.Ldarg_1);

if (paramType.IsValueType)
{
ilGenerator.Emit(OpCodes.Unbox, paramType);

if (valueTpyeOpCodes.ContainsKey(paramType))
{
OpCode load
= (OpCode)valueTpyeOpCodes[paramType];
ilGenerator.Emit(load);
}

else
{
ilGenerator.Emit(OpCodes.Ldobj, paramType);
}
}

else
{
ilGenerator.Emit(OpCodes.Castclass, paramType);
}

ilGenerator.EmitCall(OpCodes.Callvirt,
this.SetMethod, null);
ilGenerator.Emit(OpCodes.Ret);

method.DefineParameter(
1, ParameterAttributes.In, "obj");
method.DefineParameter(
2, ParameterAttributes.In, "value");
return (Action<object, object>)method.CreateDelegate(typeof(Action<object, object>));
}

  四、比較三種屬性操作的性能

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

 
public class Foo
{

public Bar Bar { get; set; }
}

public class Bar
{ }

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

 
public static void SetTest(int times)
{
Foo foo
= new Foo();
Bar bar
= new Bar();
Stopwatch stopwatch
= new Stopwatch();
PropertyAccessor
<Foo> propertyAccessor = new PropertyAccessor<Foo>("Bar");
PropertyInfo propertyInfo
= typeof(Foo).GetProperty("Bar");
stopwatch.Start();

for (int i = 0; i < times; i++)
{
foo.Bar
= bar;
}

long duration1 = stopwatch.ElapsedMilliseconds;

stopwatch.Restart();

for (int i = 0; i < times; i++)
{
propertyAccessor.Set(foo, bar);
}

long duration2 = stopwatch.ElapsedMilliseconds;

stopwatch.Restart();

for (int i = 0; i < times; i++)
{
propertyInfo.SetValue(foo, bar,
null);
}

long duration3 = stopwatch.ElapsedMilliseconds;
Console.WriteLine(
"{0,-10}{1,-10}{2,-10}{3,-10}", times, duration1, duration2, duration3);
}

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

 
public static void GetTest(int times)
{
Foo foo
= new Foo { Bar = new Bar() };
Stopwatch stopwatch
= new Stopwatch();
PropertyAccessor
<Foo> propertyAccessor = new PropertyAccessor<Foo>("Bar");
PropertyInfo propertyInfo
= typeof(Foo).GetProperty("Bar");
stopwatch.Start();

for (int i = 0; i < times; i++)
{
var bar
= foo.Bar;
}

long duration1 = stopwatch.ElapsedMilliseconds;

stopwatch.Restart();

for (int i = 0; i < times; i++)
{
var bar
= propertyAccessor.Get(foo);
}

long duration2 = stopwatch.ElapsedMilliseconds;

stopwatch.Restart();

for (int i = 0; i < times; i++)
{
var bar
= propertyInfo.GetValue(foo, null);
}

long duration3 = stopwatch.ElapsedMilliseconds;
Console.WriteLine(
"{0,-10}{1,-10}{2,-10}{3,-10}", times, duration1, duration2, duration3);
}

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

 
static void Main(string[] args)
{
Console.WriteLine(
"{0,-10}{1,-10}{2,-10}{3,-10}", "Times", "General", "IL Emit", "Reflection");
SetTest(
100000);
SetTest(
1000000);
SetTest(
10000000);

Console.WriteLine();
GetTest(
100000);
GetTest(
1000000);
GetTest(
10000000);
}

  輸出結果:

 
Times General IL Emit Reflection
100000 1 17 204
1000000 12 110 1918
10000000 131 1103 18919

100000 1 10 153
1000000 11 101 1534
10000000 112 1009 15425

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

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

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

 
private Func<object, object> CreateGetFunction()
{
var getMethod
= this.Property.GetGetMethod();
var target
= Expression.Parameter(typeof(object), "target");
var castedTarget
= getMethod.IsStatic ? null : Expression.Convert(target, this.TargetType);
var getProperty
= Expression.Property(castedTarget, this.Property);
var castPropertyValue
= Expression.Convert(getProperty, typeof(object));
return Expression.Lambda<Func<object, object>>(castPropertyValue, target).Compile();
}


private Action<object, object> CreateSetAction()
{
var setMethod
= this.Property.GetSetMethod();
var target
= Expression.Parameter(typeof(object), "target");
var propertyValue
= Expression.Parameter(typeof(object), "value");
var castedTarget
= setMethod.IsStatic ? null : Expression.Convert(target, this.TargetType);
var castedpropertyValue
= Expression.Convert(propertyValue, this.PropertyType);
var propertySet
= Expression.Call(castedTarget, setMethod, castedpropertyValue);
return Expression.Lambda<Action<object, object>>(propertySet, target, propertyValue).Compile();
}
0
0
 
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()