使用泛型委託,構築最快的通用屬性訪問器
最近做一個父類的屬性向子類的屬性賦值的小程式,用了下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開源技術團隊,做最輕最快的資料框架!