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


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

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM