性能优化列表类型转换

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

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


加载中...
01. public class ObjectConvertHelperOld
02. {
03. /// <summary>
04. /// 转换单个对象为另外一种类型对象
05. /// </summary>
06. /// <typeparam name="'TSource'">待转换的源对象类型</typeparam>
07. /// <typeparam name="'TResult'">转换的目标对象类型</typeparam>
08. ///<param name="'source'">待转换的源对象
09. /// <returns>转换的目标对象</returns>
10. public static TResult ConvertObject<tsource, tresult="">(TSource source) where TResult : new()
11. {
12. TResult result = new TResult();
13.  
14. Type sourceType = source.GetType();
15. Type resultType = result.GetType();
16.  
17. PropertyInfo[] resultProperties = resultType.GetProperties(
18. BindingFlags.Public | BindingFlags.Instance);
19.  
20. if (resultProperties != null && resultProperties.Length > 0)
21. {
22. foreach (PropertyInfo resultProperty in resultProperties)
23. {
24. if (resultProperty.PropertyType.IsGenericType)
25. {
26. continue;
27. }
28.  
29. PropertyInfo sourceProperty = sourceType.GetProperty(resultProperty.Name);
30.  
31. bool isMatched = sourceProperty != null &&
32. (!sourceProperty.PropertyType.IsGenericType) &&
33. (sourceProperty.PropertyType == resultProperty.PropertyType);
34.  
35. if (isMatched)
36. {
37. object currentValue = sourceProperty.GetValue(source, null);
38. resultProperty.SetValue(result, currentValue, null);
39. }
40.  
41. }
42. }
43. return result;
44. }
45.  
46. /// <summary>
47. /// 转换列表对象为另外一种列表对象
48. /// </summary>
49. /// <typeparam name="'TSource'">待转换的源对象类型</typeparam>
50. /// <typeparam name="'TResult'">转换的目标对象类型</typeparam>
51. ///<param name="'source'">待转换的源对象
52. /// <returns>转换的目标对象</returns>
53. public static List<tresult> ConvertList<tsource, tresult="">(List<tsource> source) where TResult : new()
54. {
55. return source.ConvertAll<tresult>(ConvertObject<tsource, tresult="">);
56. }
57.  
58. }</tsource,></tresult></tsource></tsource,></tresult></tsource,>
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字段完全相同,也不影响啊

  优化代码如下:

01. public static class ObjectConvertHelper
02. {
03. private class InnerConversion<tsource, tresult="">
04. {
05. private static readonly Func<tsource, tresult=""> s_convert;
06. static InnerConversion()
07. {
08. s_convert = BuildConvert();
09. }
10. private static Func<tsource, tresult=""> BuildConvert()
11. {//(x)=>new TResult{P1=x.p1,P2=x.p2,...};
12. var paramExp = Expression.Parameter(typeof(TSource), 'x');
13. var sourcePropertyInfos = typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance)
14. .Where(p => p.CanRead && p.CanWrite);
15. var resultPropertyInfos = typeof(TResult).GetProperties(BindingFlags.Public | BindingFlags.Instance)
16. .Where(p => p.CanRead && p.CanWrite);
17. var resultPropertyBindings = new List<memberbinding>(resultPropertyInfos.Count());
18. foreach (var item in resultPropertyInfos)
19. {
20. //不区分大小写
21. PropertyInfo piIgnoreCase = sourcePropertyInfos.Where(x => string.Compare(x.Name, item.Name, true) == 0).FirstOrDefault();
22. if (piIgnoreCase != null)
23. {
24. resultPropertyBindings.Add((MemberBinding)Expression.Bind(item, Expression.Property(paramExp, piIgnoreCase))
25. );
26. }
27. }
28. var body = Expression.MemberInit( // object initializer
29. Expression.New(typeof(TResult)), // ctor
30. resultPropertyBindings // property assignments
31. );
32. return Expression.Lambda<func<tsource, tresult="">>(body, paramExp).Compile();
33. }
34. /// <summary>
35. /// 将TSource实体转换到TResult实体(属性匹配规则:1、不区分大小写,2、两个实体属性取交集,3、TResult实体内部创建)
36. /// </summary>
37. public static Func<tsource, tresult=""> Convert
38. {
39. get
40. {
41. return s_convert;
42. }
43. }
44. }
45.  
46. /// <summary>
47. /// 将一种类型列表转换为另一种类型列表
48. /// </summary>
49. /// <typeparam name="'TSource'"></typeparam>
50. /// <typeparam name="'TResult'"></typeparam>
51. ///<param name="'sourceList'">
52. /// <returns></returns>
53. public static IList<tresult> ConvertList<tsource, tresult="">(IList<tsource> sourceList)
54. where TSource : class
55. where TResult : class,new()
56. {
57. if (sourceList == null) { throw new ArgumentNullException('sourceList'); }
58. if (sourceList.Count == 0)
59. {
60. return new List<tresult>();
61. }
62. return sourceList.Select(p => InnerConversion<tsource, tresult="">.Convert(p)).ToList();
63. }
64.  
65. public static TResult Convert<tsource, tresult="">(TSource source)
66. where TSource : class
67. where TResult : class,new()
68. {
69. if (source == null) { throw new ArgumentNullException('source'); }
70. return InnerConversion<tsource, tresult="">.Convert(source);
71. }
72. /// <summary>
73. /// 浅拷贝实体
74. /// </summary>
75. /// <typeparam name="'T'"></typeparam>
76. ///<param name="'source'">
77. /// <returns></returns>
78. public static T ShallowClone<t>(T source) where T : class,new()
79. {
80. if (source == null) { throw new ArgumentNullException('source'); }
81. return InnerConversion<t, t="">.Convert(source);
82. }
83. }</t,></t></tsource,></tsource,></tsource,></tresult></tsource></tsource,></tresult></tsource,></func<tsource,></memberbinding></tsource,></tsource,></tsource,>

类型字典(Type Dictionary):泛型类中的静态字段,会根据泛型的具体类型如InnerConversion有一份对应的静态字段,具体可看装配脑袋文章等。由于系统中的类型个数有限,这样为每种类型缓存一份转换方法,可以说一劳永逸。动态生成委托Func,很强大,可以做很多通用的功能,就像CLR帮我们写代码一样,可参考之前的《Expression Tree实践之通用Parse方法------'让CLR帮我写代码'》等。好了,下面来对比一下两者的性能吧,使用老赵的CodeTimer,测试代码如下:


加载中...
01. class SourceEntity
02. {
03. public int UserId { get; set; }
04. public string name { get; set; }
05.  
06. public string p3 { get; set; }
07. public string p4 { get; set; }
08. public string p5 { get; set; }
09. public string p6 { get; set; }
10. public string p7 { get; set; }
11. public string p8 { get; set; }
12. public string p9 { get; set; }
13. public string p10 { get; set; }
14. public string p11 { get; set; }
15.  
16. public string sourceOther { get; set; }
17. }
18.  
19. class ResultEntity
20. {
21. public int UserId { get; set; }
22. public string Name { get; set; }
23.  
24. public string P3 { get; set; }
25. public string P4 { get; set; }
26. public string P5 { get; set; }
27. public string P6 { get; set; }
28. public string P7 { get; set; }
29. public string P8 { get; set; }
30. public string P9 { get; set; }
31. public string P10 { get; set; }
32. public string P11 { get; set; }
33.  
34. public string Comment { get; set; }
35. }
36.  
37. static List<sourceentity> GenerateSources(int length)
38. {
39. List<sourceentity> result = new List<sourceentity>();
40. for (int i = 0; i < length; i++)
41. {
42. result.Add(new SourceEntity {
43. UserId=i,
44. name='stevey'+i,
45. p3=i.ToString(),
46. p4 = i.ToString(),
47. p5 = i.ToString(),
48. p6 = i.ToString(),
49. p7 = i.ToString(),
50. p8 = i.ToString(),
51. p9 = i.ToString(),
52. p10 = i.ToString(),
53. p11 = i.ToString(),
54. sourceOther='sourceOther'
55. });
56. }
57. return result;
58. }
59. public static void Main(string[] args)
60. {
61. List<sourceentity> sourceList = GenerateSources(100000);//生成测试数据
62.  
63. CodeTimer.Initialize();
64. //对于10W个元素集合执行10次转换,如下
65. CodeTimer.Time('常规反射实现的类型转换'10, () => {
66. var resultList = ObjectConvertHelperOld.ConvertList<sourceentity, resultentity="">(sourceList);
67. });
68.  
69. CodeTimer.Time('优化过的类型转换',10, () => {
70. var resultList = ObjectConvertHelper.ConvertList<sourceentity, resultentity="">(sourceList);
71. });
72.  
73. Console.ReadKey();
74. }</sourceentity,></sourceentity,></sourceentity></sourceentity></sourceentity></sourceentity>
View Code

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

\

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

执行100次的结果如下:

\

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值