1. 程式人生 > 其它 >使用泛型委託,構築最快的通用屬性訪問器

使用泛型委託,構築最快的通用屬性訪問器

最近做一個父類的屬性向子類的屬性賦值的小程式,用了下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開源技術團隊,做最輕最快的資料框架!