最近做一個父類的屬性向子類的屬性賦值的小程序,用了下AutoMapper組件,感覺不錯,想探究下它的原理,自己動手做一個例子試試看。實現這個功能,第一反應使用反射遍歷對象的屬性然后獲取父類對象的屬性值,接着設置給子類對象同名的屬性。但一想到反射的效率,就又打算才用另外的方式來實現。
搜索了下資料,發現了Artech寫的《三種屬性操作性能比較:PropertyInfo + Expression Tree + Delegate.CreateDelegate》http://www.cnblogs.com/artech/archive/2011/03/26/Propertyaccesstest.html ,文中的測試結果說明,使用委托是最快的方式,但是原文進做了原理性說明,代碼不通用,於是參照原文的方法,改寫成泛型方法了:
首先,定義一個獲取屬性值和設置屬性值的泛型委托:
public delegate T GetPropertyValue<T>(); public delegate void SetPropertyValue<T>(T Value);
然后,寫2個構造該委托實例的方法:
private static ConcurrentDictionary<string, Delegate> myDelegateCache = new ConcurrentDictionary<string, Delegate>(); public static GetPropertyValue<T> CreateGetPropertyValueDelegate<TSource, T>(TSource obj, string propertyName) { string key = string.Format("Delegate-GetProperty-{0}-{1}", typeof(TSource).FullName, propertyName); GetPropertyValue<T> result = (GetPropertyValue<T>)myDelegateCache.GetOrAdd( key, newkey => { return Delegate.CreateDelegate(typeof(GetPropertyValue<T>), obj, typeof(TSource).GetProperty(propertyName).GetGetMethod()); } ); return result; } public static SetPropertyValue<T> CreateSetPropertyValueDelegate<TSource, T>(TSource obj, string propertyName) { string key = string.Format("Delegate-SetProperty-{0}-{1}", typeof(TSource).FullName, propertyName); SetPropertyValue<T> result = (SetPropertyValue<T>)myDelegateCache.GetOrAdd( key, newkey => { return Delegate.CreateDelegate(typeof(SetPropertyValue<T>), obj, typeof(TSource).GetProperty(propertyName).GetSetMethod()); } ); return result; }
注意,上面使用了.net 4.0的線程安全的字典來緩存生成的委托類型實例:
private static ConcurrentDictionary<string, Delegate> myDelegateCache = new ConcurrentDictionary<string, Delegate>();
好了,下面我們寫一點測試代碼:
CarInfo info = new CarInfo(); info.CID = 999; //////////////// //info.ID==999; SetPropertyValue<int> set = CreateSetPropertyValueDelegate<CarInfo, int>(info, "CID"); set(100);//100; GetPropertyValue<int> get = CreateGetPropertyValueDelegate<CarInfo, int>(info, "CID"); var r = get();//100 GetPropertyValue<int> get2 = CreateGetPropertyValueDelegate<CarInfo, int>(info, "CID"); var r2 = get2();//100
經測試,結果正常,這樣,通用的最快的屬性訪問器就有了。
這個方法的性能怎么樣?聽說.net的字典查找性能不夠高,繼續寫段測試代碼跑跑看:
(注:測試代碼增加了相應的NoCache方法,代碼如下:
public static GetPropertyValue<T> CreateGetPropertyValueDelegateNoCache<TSource, T>(TSource obj, string propertyName) { return (GetPropertyValue<T>)Delegate.CreateDelegate(typeof(GetPropertyValue<T>), obj, typeof(TSource).GetProperty(propertyName).GetGetMethod()); ; } public static SetPropertyValue<T> CreateSetPropertyValueDelegateNoCache<TSource, T>(TSource obj, string propertyName) { return (SetPropertyValue<T>)Delegate.CreateDelegate(typeof(SetPropertyValue<T>), obj, typeof(TSource).GetProperty(propertyName).GetSetMethod()); ; }
)
Console.WriteLine("{0, -15}{1,-10}{2,-8}{3,-8} {4,-8}", "Times", "直接訪問", "委托(非緩存)", "委托(字典緩存)", "委托(變量緩存)"); //性能測試 直接訪問 int times = 1000000; var stopwatch = new Stopwatch(); stopwatch.Start(); for (int i = 0; i < times; i++) { int oldValue = info.CID; info.CID = i; } var duration1 = stopwatch.ElapsedMilliseconds; //使用委托,非緩存 stopwatch.Restart(); for (int i = 0; i < times; i++) { int oldValue = CreateGetPropertyValueDelegateNoCache<CarInfo, int>(info, "CID")(); CreateSetPropertyValueDelegateNoCache<CarInfo, int>(info, "CID")(i); } var duration2 = stopwatch.ElapsedMilliseconds; //使用委托,字典緩存 stopwatch.Restart(); for (int i = 0; i < times; i++) { int oldValue = CreateGetPropertyValueDelegate<CarInfo, int>(info, "CID")(); CreateSetPropertyValueDelegate<CarInfo, int>(info, "CID")(i); } var duration3 = stopwatch.ElapsedMilliseconds; //使用委托,直接緩存 stopwatch.Restart(); for (int i = 0; i < times; i++) { int oldValue = get(); set(i); } var duration4 = stopwatch.ElapsedMilliseconds; stopwatch.Stop(); Console.WriteLine("{0, -15} {1,-15} {2,-15} {3,-15} {4,-15} ", times, duration1, duration2, duration3, duration4); Console.WriteLine("--------------------單位(ms)--------------------------");
下面是運行結果:
===================Debug模式===================== Times 直接訪問 委托(非緩存) 委托(字典緩存) 委托(變量緩存) 1000000 17 12421 949 16 --------------------單位(ms)---------------------------------------------- ====================Release模式========================= Times 直接訪問 委托(非緩存) 委托(字典緩存) 委托(變量緩存) 1000000 9 11696 867 8 --------------------單位(ms)--------------------------
結果令人驚異:直接執行委托調用,比調用方法本身還要快點,另外也證明了,字典的查找性能的確不高。這個測試中字典元素的數量是較少的,有朋友提示,可能是計算字典的Key的哈希耗費了較多性能,於是將緩存字典的長度改小成DGP-{0}-{1} 和 DSP-{0}-{1},再次進行測試:
========================long key================================== Times 直接訪問 委托(非緩存) 委托(字典緩存) 委托(變量緩存) 1000000 19 11817 971 18 --------------------單位(ms)-------------------------- Times 直接訪問 委托(非緩存) 委托(字典緩存) 委托(變量緩存) 1000000 24 11210 882 16 --------------------單位(ms)-------------------------- Times 直接訪問 委托(非緩存) 委托(字典緩存) 委托(變量緩存) 1000000 22 11168 895 16 --------------------單位(ms)-------------------------- ========================short key================================== Times 直接訪問 委托(非緩存) 委托(字典緩存) 委托(變量緩存) 1000000 20 11727 689 18 --------------------單位(ms)-------------------------- imes 直接訪問 委托(非緩存) 委托(字典緩存) 委托(變量緩存) 1000000 18 11804 738 17 -------------------單位(ms)-------------------------- Times 直接訪問 委托(非緩存) 委托(字典緩存) 委托(變量緩存) 1000000 23 11234 684 16 --------------------單位(ms)--------------------------
測試結果表明,字典的Key的長度的確對性能有影響,所以我們需要注意Key不是越長越好。
補充:
下面有朋友回復說,要比較它在10次,100,10000,1000000 不同情況下面的效率,將測試程序稍微改寫了下,下面是運行結果:
Times 直接訪問 委托(非緩存) 委托(字典緩存) 委托(變量緩存) 10 0 3 0 0 --------------------單位(ms)-------------------------- * Times 直接訪問 委托(非緩存) 委托(字典緩存) 委托(變量緩存) 100 0 1 0 0 --------------------單位(ms)-------------------------- * Times 直接訪問 委托(非緩存) 委托(字典緩存) 委托(變量緩存) 10000 0 165 8 0 --------------------單位(ms)-------------------------- * Times 直接訪問 委托(非緩存) 委托(字典緩存) 委托(變量緩存) 1000000 31 11556 755 17 --------------------單位(ms)-------------------------- *
從測試來看,在執行次數在幾百次的范圍內,效率相差都是很小的,可以忽略,所以不用緩存委托結果也行。
它能做什麽?
在動態構設置對象的屬性值的地方,比如ORM的實體類屬性賦值,用途很大的。
PS:今天測試發現,代碼
Delegate.CreateDelegate(typeof(GetPropertyValue<T>), obj, typeof(TSource).GetProperty(propertyName).GetGetMethod());
創建的委托方法僅針對當前實例對象 obj 有效,除非這是靜態屬性,它並不能作為一個通用類型的屬性訪問器,所以將它緩存意義不大,但可以作為優化屬性訪問的一個手段。
------------------分界線---------------------------
歡迎加入PDF.NET開源技術團隊,做最輕最快的數據框架!