zoukankan      html  css  js  c++  java
  • 性能优化-列表类型转换(ConvertList<TSource, TResult>)

      之前,在项目中看到过一段通用列表类型转换的代码,直接的实现便是用反射。大概看了下,它用在领域模型转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次的结果如下:

      好了,就写到这里吧,如有不正之处还请指正,相互交流,共同进步~~

  • 相关阅读:
    Metabase研究 开源的数据报表
    Redis配置不当致使root被提权漏洞
    一个程序员被骗去养猪
    调度器简介,以及Linux的调度策略
    Linux的内存分页管理
    在地铁11号线上写书
    为什么说“概率”带来一场现代革命?
    快速学习Bash
    用树莓派玩转蓝牙
    树莓派的GPIO编程
  • 原文地址:https://www.cnblogs.com/skysoft001/p/3477051.html
Copyright © 2011-2022 走看看