性能優化-列表類型轉換(ConvertList )


  之前,在項目中看到過一段通用列表類型轉換的代碼,直接的實現便是用反射。大概看了下,它用在領域模型轉DTO和SOA接口中契約實體的轉換等等。首先,它使用了反射,其次,還是在循環中使用,便有了優化的想法。

方法原型如:public static List<TResult> ConvertList<TSource, TResult>(List<TSource> source) where TResult : new(),下面貼出代碼。說明一下,在此我沒有任何的貶義,這段代碼可能比較老,其次在項目中,首先是實現功能,如果當時沒有更好的實現,就先實現功能,后面有時間可以在優化,畢竟項目有時間節點,個人自己平衡哈。

public class ObjectConvertHelperOld
    {
        /// <summary>
        /// 轉換單個對象為另外一種類型對象
        /// </summary>
        /// <typeparam name="TSource">待轉換的源對象類型</typeparam>
        /// <typeparam name="TResult">轉換的目標對象類型</typeparam>
        /// <param name="source">待轉換的源對象</param>
        /// <returns>轉換的目標對象</returns>
        public static TResult ConvertObject<TSource, TResult>(TSource source) where TResult : new()
        {
            TResult result = new TResult();

            Type sourceType = source.GetType();
            Type resultType = result.GetType();

            PropertyInfo[] resultProperties = resultType.GetProperties(
                BindingFlags.Public | BindingFlags.Instance);

            if (resultProperties != null && resultProperties.Length > 0)
            {
                foreach (PropertyInfo resultProperty in resultProperties)
                {
                    if (resultProperty.PropertyType.IsGenericType)
                    {
                        continue;
                    }

                    PropertyInfo sourceProperty = sourceType.GetProperty(resultProperty.Name);

                    bool isMatched = sourceProperty != null &&
                            (!sourceProperty.PropertyType.IsGenericType) &&
                            (sourceProperty.PropertyType == resultProperty.PropertyType);

                    if (isMatched)
                    {
                        object currentValue = sourceProperty.GetValue(source, null);
                        resultProperty.SetValue(result, currentValue, null);
                    }

                }
            }
            return result;
        }

        /// <summary>
        /// 轉換列表對象為另外一種列表對象
        /// </summary>
        /// <typeparam name="TSource">待轉換的源對象類型</typeparam>
        /// <typeparam name="TResult">轉換的目標對象類型</typeparam>
        /// <param name="source">待轉換的源對象</param>
        /// <returns>轉換的目標對象</returns>
        public static List<TResult> ConvertList<TSource, TResult>(List<TSource> source) where TResult : new()
        {
            return source.ConvertAll<TResult>(ConvertObject<TSource, TResult>);
        }

    }
View Code

  從上面代碼可以看出,它核心是從TSource類型到TResult類型轉換,轉換中,1、區分大小寫,2、以TResult類型中的屬性為准,如果源類型中有,就賦值過來(實際上是取兩個實體屬性的交集),3、還考慮字段是否是泛型等等。。。

  如果熟悉Expression Tree的同學,可能就會想到,可以優化反射調用。老趙博客《表達式樹與反射調用》系列中有詳細實現,推薦大家去看看,絕對干貨!我很多這方面的知識從這里學到的,非常感謝啊!

  說一下優化思路,其實也不是什么思路了。利用類型字典LambdaExpression的Compile方法為每組轉換的類型緩存一個動態生成的委托。那么委托的調用和直接方法調用性能幾乎是一樣了。

  有時候可能會涉及平台之間的契約轉換,比如之前做的一個項目,在.net中調用第三方java的接口,java定義的契約,它的字段命名是camelCasing(小寫開頭,如:payAmount),我們之間約定是使用http post 數據傳輸格式采用json字符串,那么json字符串區分大小寫,我們兩邊都使用序列化反序列化等。我這邊就需要兩份契約了,一份是第三方接口契約實體,采用小寫開頭命名,第二份是內部契約,采用.net 命名規則PascalCasing,來定義實體屬性。這里將內部契約實體轉換成第三方契約實體,PayAmount到payAmount的對應轉換。

  之前考慮的是屬性映射區分大小寫還是不區分,由調用者參數控制,對於這個需求,簡化一下就是屬性映射不區分大小寫啦,2、以TResult類型中的字段為准(取交集),3、TResult對象的創建是在轉換內部創建的,有沒有可能這個TResult對象列表已經存在?對於為什么選擇屬性映射不區分大小寫,考慮有二,1、.net中實體中屬性的定義,一般不定義重名的(userId,UserId)2、對於TSource中字段和TResult字段完全相同,也不影響啊

  優化代碼如下:

public static class ObjectConvertHelper
    {
        private class InnerConversion<TSource, TResult>
        {
            private static readonly Func<TSource, TResult> s_convert;
            static InnerConversion()
            {
                s_convert = BuildConvert();
            }
            private static Func<TSource, TResult> BuildConvert()
            {//(x)=>new TResult{P1=x.p1,P2=x.p2,...};
                var paramExp = Expression.Parameter(typeof(TSource), "x");
                var sourcePropertyInfos = typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance)
                                                                        .Where(p => p.CanRead && p.CanWrite);
                var resultPropertyInfos = typeof(TResult).GetProperties(BindingFlags.Public | BindingFlags.Instance)
                                                                        .Where(p => p.CanRead && p.CanWrite);
                var resultPropertyBindings = new List<MemberBinding>(resultPropertyInfos.Count());
                foreach (var item in resultPropertyInfos)
                {
                    //不區分大小寫
                    PropertyInfo piIgnoreCase = sourcePropertyInfos.Where(x => string.Compare(x.Name, item.Name, true) == 0).FirstOrDefault();
                    if (piIgnoreCase != null)
                    {
                        resultPropertyBindings.Add((MemberBinding)Expression.Bind(item, Expression.Property(paramExp, piIgnoreCase))
                                                   );
                    }
                }
                var body = Expression.MemberInit( // object initializer 
                    Expression.New(typeof(TResult)), // ctor 
                    resultPropertyBindings // property assignments 
                );
                return Expression.Lambda<Func<TSource, TResult>>(body, paramExp).Compile();
            }
            /// <summary>
            /// 將TSource實體轉換到TResult實體(屬性匹配規則:1、不區分大小寫,2、兩個實體屬性取交集,3、TResult實體內部創建)
            /// </summary>
            public static Func<TSource, TResult> Convert
            {
                get
                {
                    return s_convert;
                }
            }
        }

        /// <summary>
        /// 將一種類型列表轉換為另一種類型列表
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="sourceList"></param>
        /// <returns></returns>
        public static IList<TResult> ConvertList<TSource, TResult>(IList<TSource> sourceList)
            where TSource : class
            where TResult : class,new()
        {
            if (sourceList == null) { throw new ArgumentNullException("sourceList"); }
            if (sourceList.Count == 0)
            {
                return new List<TResult>();
            }
            return sourceList.Select(p => InnerConversion<TSource, TResult>.Convert(p)).ToList();
        }

        public static TResult Convert<TSource, TResult>(TSource source)
            where TSource : class
            where TResult : class,new()
        {
            if (source == null) { throw new ArgumentNullException("source"); }
            return InnerConversion<TSource, TResult>.Convert(source);
        }
        /// <summary>
        /// 淺拷貝實體
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <returns></returns>
        public static T ShallowClone<T>(T source) where T : class,new()
        {
            if (source == null) { throw new ArgumentNullException("source"); }
            return InnerConversion<T, T>.Convert(source);
        }
    }

類型字典(Type Dictionary):泛型類中的靜態字段,會根據泛型的具體類型如InnerConversion<SourceEntity, ResultEntity>有一份對應的靜態字段,具體可看裝配腦袋文章等。由於系統中的類型個數有限,這樣為每種類型緩存一份轉換方法,可以說一勞永逸。動態生成委托Func<TSource, TResult>,很強大,可以做很多通用的功能,就像CLR幫我們寫代碼一樣,可參考之前的《Expression Tree實踐之通用Parse方法------"讓CLR幫我寫代碼"》等。好了,下面來對比一下兩者的性能吧,使用老趙的CodeTimer,測試代碼如下:

class SourceEntity
        {
            public int UserId { get; set; }
            public string name { get; set; }

            public string p3 { get; set; }
            public string p4 { get; set; }
            public string p5 { get; set; }
            public string p6 { get; set; }
            public string p7 { get; set; }
            public string p8 { get; set; }
            public string p9 { get; set; }
            public string p10 { get; set; }
            public string p11 { get; set; }

            public string sourceOther { get; set; }
        }

        class ResultEntity
        {
            public int UserId { get; set; }
            public string Name { get; set; }

            public string P3 { get; set; }
            public string P4 { get; set; }
            public string P5 { get; set; }
            public string P6 { get; set; }
            public string P7 { get; set; }
            public string P8 { get; set; }
            public string P9 { get; set; }
            public string P10 { get; set; }
            public string P11 { get; set; }

            public string Comment { get; set; }
        }

        static List<SourceEntity> GenerateSources(int length)
        {
            List<SourceEntity> result = new List<SourceEntity>();
            for (int i = 0; i < length; i++)
            {
                result.Add(new SourceEntity { 
                    UserId=i,
                    name="stevey"+i,
                    p3=i.ToString(),
                    p4 = i.ToString(),
                    p5 = i.ToString(),
                    p6 = i.ToString(),
                    p7 = i.ToString(),
                    p8 = i.ToString(),
                    p9 = i.ToString(),
                    p10 = i.ToString(),
                    p11 = i.ToString(),
                    sourceOther="sourceOther"
                });
            }
            return result;
        }
        public static void Main(string[] args)
        {
            List<SourceEntity> sourceList = GenerateSources(100000);//生成測試數據

            CodeTimer.Initialize();
            //對於10W個元素集合執行10次轉換,如下
            CodeTimer.Time("常規反射實現的類型轉換", 10, () => {
                var resultList = ObjectConvertHelperOld.ConvertList<SourceEntity, ResultEntity>(sourceList); 
            });

            CodeTimer.Time("優化過的類型轉換",10, () => {
                var resultList = ObjectConvertHelper.ConvertList<SourceEntity, ResultEntity>(sourceList);
            });

            Console.ReadKey();
        }
View Code

 在Release模式下編譯后,對於10W個元素的列表執行10次結果如下:

如果執行次數增加,還會有更大的差距,因為已經為類型緩存了委托,就幾乎相當於直接方法調用了,而老的實現每次都需要反射SetValue。但是動態編譯生成委托,這個過程比較耗時,可以作為初始化,只執行一次,后面就一勞永逸了。

執行100次的結果如下:

 

  好了,就寫到這里吧,如有不正之處還請指正,相互交流,共同進步~~

 


免責聲明!

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



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