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

  从上面代码可以看出,它核心是从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) == ).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 == )
{
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 = ; 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();//生成测试数据 CodeTimer.Initialize();
//对于10W个元素集合执行10次转换,如下
CodeTimer.Time("常规反射实现的类型转换", , () => {
var resultList = ObjectConvertHelperOld.ConvertList<SourceEntity, ResultEntity>(sourceList);
}); CodeTimer.Time("优化过的类型转换",, () => {
var resultList = ObjectConvertHelper.ConvertList<SourceEntity, ResultEntity>(sourceList);
}); Console.ReadKey();
}

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

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

执行100次的结果如下:

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

性能优化-列表类型转换(ConvertList<TSource, TResult>)的更多相关文章

  1. 列表类型转换(ConvertList<TSource, TResult>)

    性能优化-列表类型转换(ConvertList<TSource, TResult>) 2013-12-16 16:55 by stevey, 426 阅读, 7 评论, 收藏, 编辑 之前 ...

  2. Web前端性能优化总结——如何提高网页加载速度

    一.提高网页加载速度的必要性 国际知名的一组来自Jupiter Research的数据显示:购物者在访问网站过程中的不满会导致销售损失和品牌受损,其中 77%的人将不再访问网站 ,62%的人不再从该网 ...

  3. [daily][optimize] 一个小python程序的性能优化 (python类型转换函数引申的性能优化)

    前天,20161012,到望京面试.第四个职位,终于进了二面.好么,结果人力安排完了面试时间竟然没有通知我,也没有收到短信邀请.如果没有短信邀请门口的保安大哥是不让我进去大厦的.然后,我在11号接到了 ...

  4. Vue2.5开发去哪儿网App 城市列表开发之 兄弟组件间联动及列表性能优化

    一,  兄弟组件间联动 1.  点击城市字母,左侧对应显示 给遍历的 字母 添加一个点击事件: Alphabet.vue @click="handleLetterClick" ha ...

  5. h5列表页的性能优化

    //0.还原状态 caoke.loading=false $(".loadbtn").text("点击加载更多") //1 还没有任何数据的情况 if(data ...

  6. 摘:JavaScript性能优化小知识总结

    原文地址:http://www.codeceo.com/article/javascript-performance-tips.html JavaScript的性能问题不容小觑,这就需要我们开发人员在 ...

  7. 《Android开发艺术探索》读书笔记 (13) 第13章 综合技术、第14章 JNI和NDK编程、第15章 Android性能优化

    第13章 综合技术 13.1 使用CrashHandler来获取应用的Crash信息 (1)应用发生Crash在所难免,但是如何采集crash信息以供后续开发处理这类问题呢?利用Thread类的set ...

  8. s性能优化方面的小知识

    总结的js性能优化方面的小知识 前言 一直在学习javascript,也有看过<犀利开发Jquery内核详解与实践>,对这本书的评价只有两个字犀利,可能是对javascript理解的还不够 ...

  9. [转]C#程序性能优化

    C#程序性能优化 1.显式注册的EvenHandler要显式注销以避免内存泄漏 将一个成员方法注册到某个对象的事件会造成后者持有前者的引用.在事件注销之前,前者不会被垃圾回收.   private v ...

随机推荐

  1. [DP] Rod-cutting problem

    给一个长度为 n 的杆子,切成小段卖出去,价格根据小段的长度不同而不同.下面是一个例子 我们要通过切成小段卖出尽可能高的总价钱.问题是:How to decompose the problem? De ...

  2. Seven Python Tools All Data Scientists Should Know How to Use

    Seven Python Tools All Data Scientists Should Know How to Use If you’re an aspiring data scientist, ...

  3. Unity3D游戏UI开发经验谈

    原地址:http://news.9ria.com/2013/0629/27679.html 在Unity专场上,108km创始人梁伟国发表了<Unity3D游戏UI开发经验谈>主题演讲.他 ...

  4. perl的输出缓冲

      今天写一个小脚本的时候,需要即时输出当前进度到命令行上,并即时将重要数据写入报告文件中.但是perl默认是有输出缓冲的,显示到命令行上必须以\n结尾才行,输出到文件中,回车了都不行,非得要等缓冲区 ...

  5. ruby 线程学习

    i=1 Thread.start{ while true print "Thread 1 \n" i+=1 if i==5 then Thread.kill Thread.curr ...

  6. Oracle系列之包

    涉及到表的处理请参看原表结构与数据  Oracle建表插数据等等 建包 -- 建立包头 create package mypkg is procedure set_ctx(p_name in varc ...

  7. 我的第一个Struts程序

    1.程序结构 2.各种文件 LoginAction.java package com.tfj.action; public class LoginAction { private String use ...

  8. HTML5标签一览

    HTML5标签一览,点击下载 访问密码:1907

  9. phpstrom 与 xdebug 配合实现PHP单步调试

    不说废话,直接开始. 第一步: 安装并配置xdebug 安装 可以从官网直接下载对应php版本的xdebug,下载地址:  https://xdebug.org/download.php 配置,典型的 ...

  10. 函数xdes_find_bit

    使用方法 free = xdes_find_bit(descr, XDES_FREE_BIT, TRUE,hint % FSP_EXTENT_SIZE, mtr); /**************** ...