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

    性能优化-列表类型转换(ConvertList<TSource, TResult>)

    2013-12-16 16:55 by stevey, 426 阅读, 7 评论, 收藏编辑

      之前,在项目中看到过一段通用列表类型转换的代码,直接的实现便是用反射。大概看了下,它用在领域模型转DTO和SOA接口中契约实体的转换等等。首先,它使用了反射,其次,还是在循环中使用,便有了优化的想法。

    方法原型如:public static List<TResult> ConvertList<TSource, TResult>(List<TSource> source) where TResult : new(),下面贴出代码。说明一下,在此我没有任何的贬义,这段代码可能比较老,其次在项目中,首先是实现功能,如果当时没有更好的实现,就先实现功能,后面有时间可以在优化,毕竟项目有时间节点,个人自己平衡哈。

     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,测试代码如下:

     View Code

     在Release模式下编译后,对于10W个元素的列表执行10次结果如下:

    如果执行次数增加,还会有更大的差距,因为已经为类型缓存了委托,就几乎相当于直接方法调用了,而老的实现每次都需要反射SetValue。但是动态编译生成委托,这个过程比较耗时,可以作为初始化,只执行一次,后面就一劳永逸了。

    执行100次的结果如下:

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

     
     
    分类: C#
  • 相关阅读:
    POJ 1003 解题报告
    POJ 1004 解题报告
    POJ-1002 解题报告
    vi--文本编辑常用快捷键之光标移动
    常用图表工具
    September 05th 2017 Week 36th Tuesday
    September 04th 2017 Week 36th Monday
    September 03rd 2017 Week 36th Sunday
    September 02nd 2017 Week 35th Saturday
    September 01st 2017 Week 35th Friday
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3477638.html
Copyright © 2011-2022 走看看