【前言】
OOM框架想必大家在Web开发中是使用频率非常之高的,如果还不甚了解OOM框架,那么我们对OOM框架稍作讲解。
OOM顾名思义,Object-Object-Mapping实体间相互转换。常见的使用场景有两个实体要通过DTO对象进行页面的渲染,那么我们就需要通过对DTO对象的一个一个属性进行赋值,最终返回。整个过程是单调又繁琐的,甚至严重影响了代码的整洁性。更有强迫症高度患者可能看着这一坨shi一样的代码阵阵痉挛...因此,我们就想,能不能用一种简介的方法进行自动映射两个,三个甚至更多的对象,避免写这么一些低级乏味的代码。
于是乎,一大波一大波的自动映射框架悠然而生。业内比较出名的有:AutoMapper,EmitMapper,NLiteMapper,TinyMapper等。在这里,先抛去他们不谈,毕竟未必个人觉得好用,或者我就喜欢造轮子。本篇将讲解一波自己造的轮子OOM框架AutoMapper,并且有造轮子过程中的持续优化,我想,这便是学习的最好过程吧。
本文讲解了反射方式的实现方法以及后续抛弃反射的优化方法Expression Tree 表达式树的实现方法。
【实现思路】
AutoMapper既然是OOM框架,那么就离不开Object,Object必然又涉及到类型。那么,泛型是必不可少的。
框架要简洁易用,并且能兼容多数场景。于是,我们准备从以下几方面进行设计:
- 支持自动映射,能通过调用一个方法自动映射实体;
- 支持特殊属性特殊处理的扩展功能,特殊处理的部分可以特殊进行赋值;
- 有不想进行映射的属性能够进行屏蔽;
- 映射的名称要支持配置(有名字不同的场景无法自动映射,手动配置映射关系进行映射);
- 配置不能加配置文件,通过标签的方式(约定优于配置);
- 性能要卓越;
- 代码要通用,版本支持度要高(使用.net standard类库,可同时支持.net framework 和 .netcore);
【实现过程】
项目结构:
依据上面的思路,第一时间想到的便是通过反射获取到第一个对象的属性值,并动态创建第二个对象,遍历属性赋值,再添加细节处理。SoEasy!于是便兴冲冲完成了第一版。
首先构造了几个特性标签类,用于标注是否要映射,以及映射的自定义别名的配置。
DoNotMapperAttribute:不进行映射该属性
MapperAttribute:添加自定义名称的映射配置
MapperClassAttribute:该类支持映射(目前没有实际应用)
1 using System; 2 3 namespace SevenTiny.Bantina.AutoMapper 4 { 5 [AttributeUsage(AttributeTargets.Property, Inherited = true)] 6 public class DoNotMapperAttribute :Attribute 7 { 8 } 9 }
1 using System; 2 using System.Linq; 3 using System.Reflection; 4 5 namespace SevenTiny.Bantina.AutoMapper 6 { 7 [AttributeUsage(AttributeTargets.Property, Inherited = true)] 8 public class MapperAttribute : Attribute 9 { 10 public string TargetName { get; set; } 11 12 public MapperAttribute() { } 13 public MapperAttribute(string targetName) 14 { 15 this.TargetName = targetName; 16 } 17 18 public static string GetTargetName(PropertyInfo property) 19 { 20 var attr = property.GetCustomAttributes<MapperAttribute>(true).FirstOrDefault(); 21 return attr != null ? (attr as MapperAttribute).TargetName ?? default(string) : default(string); 22 } 23 } 24 }
1 using System; 2 using System.Linq; 3 4 namespace SevenTiny.Bantina.AutoMapper 5 { 6 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field, Inherited = true)] 7 public class MapperClassAttribute : Attribute 8 { 9 public string Name { get; set; } 10 public static string GetName(Type type) 11 { 12 var attr = type.GetCustomAttributes(typeof(MapperClassAttribute), true).FirstOrDefault(); 13 return attr != null ? (attr as MapperClassAttribute).Name ?? default(string) : default(string); 14 } 15 } 16 }
第一版反射实现代码:
1 public sealed class Mapper 2 { 3 private Mapper() { } 4 /// <summary> 5 /// Init Source Value Dic 6 /// </summary> 7 /// <typeparam name="TSource"></typeparam> 8 /// <param name="source"></param> 9 /// <returns></returns> 10 private static Dictionary<string, object> Initdic<TSource>(TSource source) 11 { 12 Dictionary<string, object> dic = new Dictionary<string, object>(); 13 foreach (PropertyInfo property in typeof(TSource).GetProperties()) 14 { 15 string targetPropertyName = MapperAttribute.GetTargetName(property); 16 if (!string.IsNullOrEmpty(targetPropertyName)) 17 { 18 if (!dic.ContainsKey(targetPropertyName)) 19 { 20 dic.Add(targetPropertyName, property.GetValue(source)); 21 } 22 } 23 else if (!dic.ContainsKey(property.Name)) 24 { 25 dic.Add(property.Name, property.GetValue(source)); 26 } 27 } 28 return dic; 29 } 30 /// <summary> 31 /// SetValue from propertyinfo and sourceDictionary 32 /// </summary> 33 /// <typeparam name="TValue"></typeparam> 34 /// <param name="value"></param> 35 /// <param name="propertyInfos"></param> 36 /// <param name="sourceDic"></param> 37 /// <returns></returns> 38 private static TValue SetValue<TValue>(TValue value, PropertyInfo[] propertyInfos, Dictionary<string, object> sourceDic, Dictionary<string, string> keys) where TValue : class 39 { 40 foreach (PropertyInfo property in propertyInfos) 41 { 42 if (!keys.ContainsKey(property.Name)) 43 { 44 if (sourceDic.ContainsKey(property.Name)) 45 { 46 try 47 { 48 property.SetValue(value, sourceDic[property.Name]); 49 keys.Add(property.Name, string.Empty); 50 } 51 catch (Exception) 52 { 53 property.SetValue(value, null); 54 } 55 } 56 } 57 } 58 return value; 59 } 60 /// <summary> 61 /// AutoMapper 62 /// </summary> 63 /// <typeparam name="TValue">value type</typeparam> 64 /// <typeparam name="TSource">source type</typeparam> 65 /// <param name="source"></param> 66 /// <returns></returns> 67 public static TValue AutoMapper<TValue, TSource>(TSource source) where TValue : class where TSource : class 68 { 69 TValue value = Activator.CreateInstance<TValue>(); 70 PropertyInfo[] propertyInfos = typeof(TValue).GetProperties(); 71 Dictionary<string, string> keys = new Dictionary<string, string>(); 72 value = SetValue(value, propertyInfos, Initdic(source), keys); 73 return value; 74 } 75 /// <summary> 76 /// AutoMapper,Support for Use Action to custom special fields. 77 /// </summary> 78 /// <typeparam name="TValue"></typeparam> 79 /// <typeparam name="TSource"></typeparam> 80 /// <param name="source"></param> 81 /// <param name="action"></param> 82 /// <returns></returns> 83 public static TValue AutoMapper<TValue, TSource>(TSource source, Action<TValue> action) where TValue : class where TSource : class 84 { 85 TValue value = Activator.CreateInstance<TValue>(); 86 PropertyInfo[] propertyInfos = typeof(TValue).GetProperties(); 87 Dictionary<string, string> keys = new Dictionary<string, string>(); 88 value = SetValue(value, propertyInfos, Initdic(source), keys); 89 action(value); 90 return value; 91 } 92 }
由于思路清晰,实现起来也是很容易的。
通过传入两个泛型(一个输入,一个输出),然后获取到其中一个的属性,并遍历另一个的属性,如果相同,则直接赋值(当然有特性标签部分的细节处理)。
由于有些属性已经赋值过了,再次遇到会重新扫描赋值,降低了性能和影响了逻辑性,这里做了Dictionary的暂时缓存,以便扫描过的值不再进行赋值。
特殊的值,我们使用了Action<T> action 匿名委托的方式进行赋值,提供了特殊处理的能力。
我们下面进行一波使用,简单封装一个多次调用的测试方法:
1 public static TimeSpan Caculate(int executTimes, Action action) 2 { 3 Stopwatch sw = new Stopwatch(); 4 sw.Start(); 5 for (int i = 0; i < executTimes; i++) 6 { 7 action(); 8 } 9 sw.Stop(); 10 return sw.Elapsed; 11 }
简单写一个控制台应用程序,并传入参数100万次调用,并打印执行用时:
1 Student1 stu1 = new Student1 { Uid = Guid.NewGuid() }; 2 Student5 stu5 = new Student5 { HealthLevel = 100, SchoolClass = new SchoolClass { Name = "class1" } }; 3 4 var test1 = StopwatchHelper.Caculate(1000000, () => 5 { 6 Student stu = Mapper.AutoMapper<Student, Student5>(stu5, t => t.Name = "jony"); 7 }); 8 Console.WriteLine(test1.TotalMilliseconds);
执行100w次大概需要3654毫秒,也就是3.6秒。
虽说100万次小程序是可以忽略的,但是对于高并发的场景100万次再平常不过了,这个性能是不能忍受的!
再看一眼直接调用的性能:
1 Student5 stu5 = new Student5 { HealthLevel = 100, SchoolClass = new SchoolClass { Name = "class1" } }; 2 3 var test1 = StopwatchHelper.Caculate(1000000, () => 4 { 5 Student stu = new Student { HealthLevel = stu5.HealthLevel, SchoolClass = stu5.SchoolClass }; 6 }); 7 Console.WriteLine(test1.TotalMilliseconds);
直接最简单New对象赋值的操作耗时是100万次34毫秒,也就是0.034秒的样子差距还是挺大的。
可见这样的性能是注定不可能成为一个好用的组件的!更别谈高性能了。
于是便开始了在茫茫的代码库中淘取黑科技的一波操作。很感谢现代的搜索引擎,让我能在迷失的黑夜中找到一盏明灯。
反射造成的性能问题是我迫切要解决的,反射性能的规避通常有以下几种方式进行提升:
- 缓存,高并发场景下绝对是要避免每次都去调用反射代码的,将反射部分尽量缓存下来。
- 调用C/C++代码库,但是首先要懂C/C++,其次要保证调用dll的时间要将反射的时间换取回来。
- 通过Expression Tree的方式构造表达式树,然后将表达式树缓存起来,这样在之后调用的代码都是等价于直接执行代码的。
- 通过IL Emit的方式动态构造代码,更为底层的IL代码提供更高的效率。
在这四种方案中,我聚焦到了最后两种上。
Activator.CreateInstance<TValue>()
因为反射即便再怎么缓存,上述构建新实例的方法也是很耗费性能的(更别说由于临时工赶.net类库的问题,哈哈哈,太搞笑,可以参考此博文跟随老一辈程序员关于该方法性能的探讨以及源码的剖析http://www.cnblogs.com/leven/archive/2009/12/08/instanse_create_comparison.html),其次还有通过属性去获取值。这些都是难以通过缓存解决的。
IL Emit的实现方式是比较复杂的,稍一不慎,还容易造成内存泄漏等严重的问题。让我一个半吊子程序员来写这么细致的代码,暂时选择回避。
我选择了第三种:通过Expression Tree的方式构造表达式树,并且缓存委托方法进行调用的方法。表达式树这里就不进行详细讲解了,会放在其他博文里面单独讲解(也是一大块学问哦)。
和第一版大同小异,也是通过泛型的思想进行构建。不同的是泛型从方法上传递改为了类上传递,这也是由于缓存Func部分的泛型难以直接传递转换的方式,这个对使用并无太大影响。
第二版 Expression Tree 表达式树方式实现代码:
为了代码通用,抽取了公共部分构造了一个内部的类进行对表达式树的构建操作:
1 /********************************************************* 2 * CopyRight: 7TINY CODE BUILDER. 3 * Version: 5.0.0 4 * Author: 7tiny 5 * Address: Earth 6 * Create: 2018-04-09 16:55:16 7 * Modify: 2018-04-09 16:55:16 8 * E-mail: dong@7tiny.com | sevenTiny@foxmail.com 9 * GitHub: https://github.com/sevenTiny 10 * Personal web site: http://www.7tiny.com 11 * Technical WebSit: http://www.cnblogs.com/7tiny/ 12 * Description: 13 * Thx , Best Regards ~ 14 *********************************************************/ 15 using System; 16 using System.Collections.Generic; 17 using System.Linq; 18 using System.Linq.Expressions; 19 using System.Reflection; 20 21 namespace SevenTiny.Bantina.AutoMapper 22 { 23 internal sealed class MapperExpressionCommon 24 { 25 /// <summary> 26 /// structure func 27 /// </summary> 28 /// <param name="outType"></param> 29 /// <param name="inTypes"></param> 30 /// <param name="memberInitExpression"></param> 31 /// <param name="parameterExpressionList"></param> 32 public static void GetFunc(Type outType, Type[] inTypes, out MemberInitExpression memberInitExpression, out List<ParameterExpression> parameterExpressionList) 33 { 34 parameterExpressionList = new List<ParameterExpression>(); 35 List<MemberBinding> memberBindingList = new List<MemberBinding>(); 36 PropertyInfo[] propertyInfos = outType.GetProperties(); 37 Dictionary<string, PropertyInfo> outPropertyDic = propertyInfos.ToDictionary(t => t.Name, t => t); 38 foreach (var inType in inTypes) 39 { 40 ParameterExpression parameterExpression = Expression.Parameter(inType, inType.FullName); 41 PropertyInfo[] inTypePpropertyInfos = inType.GetProperties(); 42 foreach (var inTypeInfo in inTypePpropertyInfos) 43 { 44 if (inTypeInfo.GetCustomAttribute(typeof(DoNotMapperAttribute)) == null) 45 { 46 //first 47 string outPropertyDicKey = MapperAttribute.GetTargetName(inTypeInfo); 48 //second 49 if (string.IsNullOrEmpty(outPropertyDicKey) && outPropertyDic.Keys.Contains(inTypeInfo.Name)) 50 { 51 outPropertyDicKey = inTypeInfo.Name; 52 } 53 //third 54 if (!string.IsNullOrEmpty(outPropertyDicKey) && outPropertyDic.Keys.Contains(outPropertyDicKey)) 55 { 56 MemberExpression property = Expression.Property(parameterExpression, inTypeInfo); 57 MemberBinding memberBinding = Expression.Bind(outPropertyDic[outPropertyDicKey], property); 58 memberBindingList.Add(memberBinding); 59 outPropertyDic.Remove(outPropertyDicKey);//remove property if has be valued 60 } 61 } 62 } 63 if (!parameterExpressionList.Exists(t => t.Name.Equals(parameterExpression.Name))) 64 { 65 parameterExpressionList.Add(parameterExpression); 66 } 67 } 68 memberInitExpression = Expression.MemberInit(Expression.New(outType), memberBindingList.ToArray()); 69 } 70 } 71 72 }
在上面的这段代码中:
ParameterExpression parameterExpression = Expression.Parameter(inType, inType.FullName);
构建了类似 t 的结构。
MemberExpression property = Expression.Property(parameterExpression, inTypeInfo);
MemberBinding memberBinding = Expression.Bind(outPropertyDic[outPropertyDicKey], property);
构建了类似 HealthLevel = t.HealthLevel 的结构。
1 memberInitExpression = Expression.MemberInit(Expression.New(outType), memberBindingList.ToArray());
构建了类似 new Student() {HealthLevel = t.HealthLevel, SchoolClass = t.SchoolClass} 的结构。
这段逻辑代码的调用方代码:
1 /********************************************************* 2 * CopyRight: 7TINY CODE BUILDER. 3 * Version: 5.0.0 4 * Author: 7tiny 5 * Address: Earth 6 * Create: 2018-03-16 10:11:43 7 * Modify: 2018-4-3 11:35:53 8 * E-mail: dong@7tiny.com | sevenTiny@foxmail.com 9 * GitHub: https://github.com/sevenTiny 10 * Personal web site: http://www.7tiny.com 11 * Technical WebSit: http://www.cnblogs.com/7tiny/ 12 * Description: 13 * Thx , Best Regards ~ 14 *********************************************************/ 15 using System; 16 using System.Collections.Generic; 17 using System.Linq; 18 using System.Linq.Expressions; 19 using System.Reflection; 20 21 namespace SevenTiny.Bantina.AutoMapper 22 { 23 public sealed class Mapper<TIn, TOut> where TOut : class where TIn : class 24 { 25 private Mapper() { } 26 private static readonly Func<TIn, TOut> funcCache = GetFunc(); 27 public static TOut AutoMapper(TIn tIn) 28 { 29 return funcCache(tIn); 30 } 31 public static TOut AutoMapper(TIn tIn, Action<TOut> action) 32 { 33 TOut outValue = funcCache(tIn); 34 action(outValue); 35 return outValue; 36 } 37 private static Func<TIn, TOut> GetFunc() 38 { 39 Type[] types = new Type[] { typeof(TIn) }; 40 MemberInitExpression memberInitExpression; 41 List<ParameterExpression> parameterExpressionList; 42 MapperExpressionCommon.GetFunc(typeof(TOut), types, out memberInitExpression, out parameterExpressionList); 43 Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, parameterExpressionList); 44 return lambda.Compile(); 45 } 46 } 47 }
在上面这段代码中:
Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, parameterExpressionList);
使用上面的公共方法out出的参数构建了一个完整的lambda表达式 t => new Student() {HealthLevel = t.HealthLevel, SchoolClass = t.SchoolClass}
从代码断点中也可以看出:
这里的t由于公共方法中使用的是 type(T).FullName,所以看起来比较长,是Test.SevenTiny.Bantina.Model.Student5,可以看成是一个小写的“t”,这里是等效的。
lambda.Compile()
将上述的表达式执行为Func<TIn,TOut> 的一个匿名委托。
private static readonly Func<TIn, TOut> funcCache = GetFunc();
将执行后的结果(匿名委托)缓存起来。
这样最终的执行结果便转换成了一段代码:
Func<Student5,Student> stuFunc = t=>new Student() {HealthLevel = t.HealthLevel, SchoolClass = t.SchoolClass};
调用这段代码和直接实例化方法赋值是等效的,但是调用这段代码可以将很多通用的方法封装成一个公共的方法,提高了代码的重用性。
这段代码耗时的部分便是通过一定的反射构建出一个Func<T,T2>所耗费的时间,这在数百万次的调用中仅仅调用了一次,而执行部分却是不耗费时间的,在高并发的场景下占有很大的优势。
下面我们实际测试一下:
同样的代码,使用了第二版的AutoMapper,执行100万次
1 Student5 stu5 = new Student5 { HealthLevel = 100, SchoolClass = new SchoolClass { Name = "class1" } }; 2 3 var test1 = StopwatchHelper.Caculate(1000000, () => 4 { 5 Student stu = Mapper<Student5, Student>.AutoMapper(stu5, t => t.Name = "jony"); 6 }); 7 Console.WriteLine(test1.TotalMilliseconds);
我们加大调用的次数再次进行比较:
1 Student5 stu5 = new Student5 { HealthLevel = 100, SchoolClass = new SchoolClass { Name = "class1" } }; 2 3 var test0 = StopwatchHelper.Caculate(1000000, () => 4 { 5 Student stu = Mapper.AutoMapper<Student,Student5>(stu5, t => t.Name = "jony"); 6 }); 7 Console.WriteLine("使用反射调用 1 百万次耗时:"); 8 Console.WriteLine(test0.TotalMilliseconds); 9 10 Console.WriteLine(); 11 12 var test1 = StopwatchHelper.Caculate(1000000, () => 13 { 14 Student stu = Mapper<Student5, Student>.AutoMapper(stu5, t => t.Name = "jony"); 15 }); 16 Console.WriteLine("使用Expression表达式树调用 1 百万次耗时:"); 17 Console.WriteLine(test1.TotalMilliseconds); 18 19 Console.WriteLine(); 20 21 var test2 = StopwatchHelper.Caculate(1000000, () => 22 { 23 Student stu = new Student { HealthLevel = stu5.HealthLevel, Name = "jony" }; 24 }); 25 Console.WriteLine("使用代码直接构建 1 百万次耗时:"); 26 Console.WriteLine(test2.TotalMilliseconds);
一百万次代码执行,Expression表达式树方式耗时0.084秒,而直接写代码赋值的方式耗时0.022秒,但是使用反射的方式却使用了3.6秒。
执行次数越多,Expression表达式树的性能便越接近直接调用代码的方式。
【总结】
通过本次基础组建的完成以及一次优化,我从中学习到了Expression表达式树的实现方法以及突出的性能优势,虽然相比反射的写法更加复杂一些。
在接下来的优化中,可能会加入对Emit的支持,到时候便是一场Emit和Expression的大战,不过我再次预测结果:应该差距不是很大~ 敬请期待...
本文的完整代码以在我的github中开源,可以直接clone下来查看验证。
https://github.com/sevenTiny/SevenTiny.Bantina
下载下来的项目有多种基础组件,直接查看该组件,当然有对其他组件的意见或建议也可以直接提出来探讨,共同学习哦~
不便于github查看的同学,我这里提供了完整的代码(是有多个对象映射的重载方法的完整版本)。
公共Attribute特性标签部分:
1 /********************************************************* 2 * CopyRight: 7TINY CODE BUILDER. 3 * Version: 5.0.0 4 * Author: 7tiny 5 * Address: Earth 6 * Create: 2018-04-09 16:55:16 7 * Modify: 2018-04-09 16:55:16 8 * E-mail: dong@7tiny.com | sevenTiny@foxmail.com 9 * GitHub: https://github.com/sevenTiny 10 * Personal web site: http://www.7tiny.com 11 * Technical WebSit: http://www.cnblogs.com/7tiny/ 12 * Description: 13 * Thx , Best Regards ~ 14 *********************************************************/ 15 using System; 16 17 namespace SevenTiny.Bantina.AutoMapper 18 { 19 [AttributeUsage(AttributeTargets.Property, Inherited = true)] 20 public class DoNotMapperAttribute :Attribute 21 { 22 } 23 }
1 /********************************************************* 2 * CopyRight: 7TINY CODE BUILDER. 3 * Version: 5.0.0 4 * Author: 7tiny 5 * Address: Earth 6 * Create: 2018-04-03 13:28:38 7 * Modify: 2018-04-03 13:28:38 8 * E-mail: dong@7tiny.com | sevenTiny@foxmail.com 9 * GitHub: https://github.com/sevenTiny 10 * Personal web site: http://www.7tiny.com 11 * Technical WebSit: http://www.cnblogs.com/7tiny/ 12 * Description: 13 * Thx , Best Regards ~ 14 *********************************************************/ 15 using System; 16 using System.Linq; 17 using System.Reflection; 18 19 namespace SevenTiny.Bantina.AutoMapper 20 { 21 [AttributeUsage(AttributeTargets.Property, Inherited = true)] 22 public class MapperAttribute : Attribute 23 { 24 public string TargetName { get; set; } 25 26 public MapperAttribute() { } 27 public MapperAttribute(string targetName) 28 { 29 this.TargetName = targetName; 30 } 31 32 public static string GetTargetName(PropertyInfo property) 33 { 34 var attr = property.GetCustomAttributes<MapperAttribute>(true).FirstOrDefault(); 35 return attr != null ? (attr as MapperAttribute).TargetName ?? default(string) : default(string); 36 } 37 } 38 }
1 /********************************************************* 2 * CopyRight: 7TINY CODE BUILDER. 3 * Version: 5.0.0 4 * Author: 7tiny 5 * Address: Earth 6 * Create: 2018-04-03 13:28:38 7 * Modify: 2018-04-03 13:28:38 8 * E-mail: dong@7tiny.com | sevenTiny@foxmail.com 9 * GitHub: https://github.com/sevenTiny 10 * Personal web site: http://www.7tiny.com 11 * Technical WebSit: http://www.cnblogs.com/7tiny/ 12 * Description: 13 * Thx , Best Regards ~ 14 *********************************************************/ 15 using System; 16 using System.Linq; 17 18 namespace SevenTiny.Bantina.AutoMapper 19 { 20 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field, Inherited = true)] 21 public class MapperClassAttribute : Attribute 22 { 23 public string Name { get; set; } 24 public static string GetName(Type type) 25 { 26 var attr = type.GetCustomAttributes(typeof(MapperClassAttribute), true).FirstOrDefault(); 27 return attr != null ? (attr as MapperClassAttribute).Name ?? default(string) : default(string); 28 } 29 } 30 }
一、反射版本
1 /********************************************************* 2 * CopyRight: 7TINY CODE BUILDER. 3 * Version: 5.0.0 4 * Author: 7tiny 5 * Address: Earth 6 * Create: 2018-03-16 10:11:43 7 * Modify: 2018-4-3 11:35:53 8 * E-mail: dong@7tiny.com | sevenTiny@foxmail.com 9 * GitHub: https://github.com/sevenTiny 10 * Personal web site: http://www.7tiny.com 11 * Technical WebSit: http://www.cnblogs.com/7tiny/ 12 * Description: 13 * Thx , Best Regards ~ 14 *********************************************************/ 15 using System; 16 using System.Collections.Generic; 17 using System.Linq; 18 using System.Linq.Expressions; 19 using System.Reflection; 20 21 namespace SevenTiny.Bantina.AutoMapper 22 { 23 public sealed class Mapper 24 { 25 private Mapper() { } 26 /// <summary> 27 /// Init Source Value Dic 28 /// </summary> 29 /// <typeparam name="TSource"></typeparam> 30 /// <param name="source"></param> 31 /// <returns></returns> 32 private static Dictionary<string, object> Initdic<TSource>(TSource source) 33 { 34 Dictionary<string, object> dic = new Dictionary<string, object>(); 35 foreach (PropertyInfo property in typeof(TSource).GetProperties()) 36 { 37 string targetPropertyName = MapperAttribute.GetTargetName(property); 38 if (!string.IsNullOrEmpty(targetPropertyName)) 39 { 40 if (!dic.ContainsKey(targetPropertyName)) 41 { 42 dic.Add(targetPropertyName, property.GetValue(source)); 43 } 44 } 45 else if (!dic.ContainsKey(property.Name)) 46 { 47 dic.Add(property.Name, property.GetValue(source)); 48 } 49 } 50 return dic; 51 } 52 /// <summary> 53 /// SetValue from propertyinfo and sourceDictionary 54 /// </summary> 55 /// <typeparam name="TValue"></typeparam> 56 /// <param name="value"></param> 57 /// <param name="propertyInfos"></param> 58 /// <param name="sourceDic"></param> 59 /// <returns></returns> 60 private static TValue SetValue<TValue>(TValue value, PropertyInfo[] propertyInfos, Dictionary<string, object> sourceDic, Dictionary<string, string> keys) where TValue : class 61 { 62 foreach (PropertyInfo property in propertyInfos) 63 { 64 if (!keys.ContainsKey(property.Name)) 65 { 66 if (sourceDic.ContainsKey(property.Name)) 67 { 68 try 69 { 70 property.SetValue(value, sourceDic[property.Name]); 71 keys.Add(property.Name, string.Empty); 72 } 73 catch (Exception) 74 { 75 property.SetValue(value, null); 76 } 77 } 78 } 79 } 80 return value; 81 } 82 /// <summary> 83 /// AutoMapper 84 /// </summary> 85 /// <typeparam name="TValue">value type</typeparam> 86 /// <typeparam name="TSource">source type</typeparam> 87 /// <param name="source"></param> 88 /// <returns></returns> 89 public static TValue AutoMapper<TValue, TSource>(TSource source) where TValue : class where TSource : class 90 { 91 TValue value = Activator.CreateInstance<TValue>(); 92 PropertyInfo[] propertyInfos = typeof(TValue).GetProperties(); 93 Dictionary<string, string> keys = new Dictionary<string, string>(); 94 value = SetValue(value, propertyInfos, Initdic(source), keys); 95 return value; 96 } 97 /// <summary> 98 /// AutoMapper,Support for Use Action to custom special fields. 99 /// </summary> 100 /// <typeparam name="TValue"></typeparam> 101 /// <typeparam name="TSource"></typeparam> 102 /// <param name="source"></param> 103 /// <param name="action"></param> 104 /// <returns></returns> 105 public static TValue AutoMapper<TValue, TSource>(TSource source, Action<TValue> action) where TValue : class where TSource : class 106 { 107 TValue value = Activator.CreateInstance<TValue>(); 108 PropertyInfo[] propertyInfos = typeof(TValue).GetProperties(); 109 Dictionary<string, string> keys = new Dictionary<string, string>(); 110 value = SetValue(value, propertyInfos, Initdic(source), keys); 111 action(value); 112 return value; 113 } 114 /// <summary> 115 /// AutoMapper with multitype properties. 116 /// </summary> 117 /// <typeparam name="TValue"></typeparam> 118 /// <typeparam name="TSource1"></typeparam> 119 /// <typeparam name="TSource2"></typeparam> 120 /// <param name="source1"></param> 121 /// <param name="source2"></param> 122 /// <returns></returns> 123 public static TValue AutoMapper<TValue, TSource1, TSource2>(TSource1 source1, TSource2 source2) where TValue : class where TSource1 : class where TSource2 : class 124 { 125 TValue value = Activator.CreateInstance<TValue>(); 126 PropertyInfo[] propertyInfos = typeof(TValue).GetProperties(); 127 Dictionary<string, string> keys = new Dictionary<string, string>(); 128 value = SetValue(value, propertyInfos, Initdic(source1), keys); 129 value = SetValue(value, propertyInfos, Initdic(source2), keys); 130 return value; 131 } 132 /// <summary> 133 /// AutoMapper with multitype properties.Support for Use Action to custom special fields. 134 /// </summary> 135 /// <typeparam name="TValue"></typeparam> 136 /// <typeparam name="TSource1"></typeparam> 137 /// <typeparam name="TSource2"></typeparam> 138 /// <param name="source1"></param> 139 /// <param name="source2"></param> 140 /// <param name="action"></param> 141 /// <returns></returns> 142 public static TValue AutoMapper<TValue, TSource1, TSource2>(TSource1 source1, TSource2 source2, Action<TValue> action) where TValue : class where TSource1 : class where TSource2 : class 143 { 144 TValue value = Activator.CreateInstance<TValue>(); 145 PropertyInfo[] propertyInfos = typeof(TValue).GetProperties(); 146 Dictionary<string, string> keys = new Dictionary<string, string>(); 147 value = SetValue(value, propertyInfos, Initdic(source1), keys); 148 value = SetValue(value, propertyInfos, Initdic(source2), keys); 149 action(value); 150 return value; 151 } 152 /// <summary> 153 /// AutoMapper with multitype properties. 154 /// </summary> 155 /// <typeparam name="TValue"></typeparam> 156 /// <typeparam name="TSource1"></typeparam> 157 /// <typeparam name="TSource2"></typeparam> 158 /// <typeparam name="TSource3"></typeparam> 159 /// <param name="source1"></param> 160 /// <param name="source2"></param> 161 /// <param name="source3"></param> 162 /// <returns></returns> 163 public static TValue AutoMapper<TValue, TSource1, TSource2, TSource3>(TSource1 source1, TSource2 source2, TSource3 source3) where TValue : class where TSource1 : class where TSource2 : class where TSource3 : class 164 { 165 TValue value = Activator.CreateInstance<TValue>(); 166 PropertyInfo[] propertyInfos = typeof(TValue).GetProperties(); 167 Dictionary<string, string> keys = new Dictionary<string, string>(); 168 value = SetValue(value, propertyInfos, Initdic(source1), keys); 169 value = SetValue(value, propertyInfos, Initdic(source2), keys); 170 value = SetValue(value, propertyInfos, Initdic(source3), keys); 171 return value; 172 } 173 /// <summary> 174 /// AutoMapper with multitype properties.Support for Use Action to custom special fields. 175 /// </summary> 176 /// <typeparam name="TValue"></typeparam> 177 /// <typeparam name="TSource1"></typeparam> 178 /// <typeparam name="TSource2"></typeparam> 179 /// <typeparam name="TSource3"></typeparam> 180 /// <param name="source1"></param> 181 /// <param name="source2"></param> 182 /// <param name="source3"></param> 183 /// <param name="action"></param> 184 /// <returns></returns> 185 public static TValue AutoMapper<TValue, TSource1, TSource2, TSource3>(TSource1 source1, TSource2 source2, TSource3 source3, Action<TValue> action) where TValue : class where TSource1 : class where TSource2 : class where TSource3 : class 186 { 187 TValue value = Activator.CreateInstance<TValue>(); 188 PropertyInfo[] propertyInfos = typeof(TValue).GetProperties(); 189 Dictionary<string, string> keys = new Dictionary<string, string>(); 190 value = SetValue(value, propertyInfos, Initdic(source1), keys); 191 value = SetValue(value, propertyInfos, Initdic(source2), keys); 192 value = SetValue(value, propertyInfos, Initdic(source3), keys); 193 action(value); 194 return value; 195 } 196 /// <summary> 197 /// AutoMapper with multitype properties. 198 /// </summary> 199 /// <typeparam name="TValue"></typeparam> 200 /// <typeparam name="TSource1"></typeparam> 201 /// <typeparam name="TSource2"></typeparam> 202 /// <typeparam name="TSource3"></typeparam> 203 /// <typeparam name="TSource4"></typeparam> 204 /// <param name="source1"></param> 205 /// <param name="source2"></param> 206 /// <param name="source3"></param> 207 /// <param name="source4"></param> 208 /// <returns></returns> 209 public static TValue AutoMapper<TValue, TSource1, TSource2, TSource3, TSource4>(TSource1 source1, TSource2 source2, TSource3 source3, TSource4 source4) where TValue : class where TSource1 : class where TSource2 : class where TSource3 : class where TSource4 : class 210 { 211 TValue value = Activator.CreateInstance<TValue>(); 212 PropertyInfo[] propertyInfos = typeof(TValue).GetProperties(); 213 Dictionary<string, string> keys = new Dictionary<string, string>(); 214 value = SetValue(value, propertyInfos, Initdic(source1), keys); 215 value = SetValue(value, propertyInfos, Initdic(source2), keys); 216 value = SetValue(value, propertyInfos, Initdic(source3), keys); 217 value = SetValue(value, propertyInfos, Initdic(source4), keys); 218 return value; 219 } 220 /// <summary> 221 /// AutoMapper with multitype properties.Support for Use Action to custom special fields. 222 /// </summary> 223 /// <typeparam name="TValue"></typeparam> 224 /// <typeparam name="TSource1"></typeparam> 225 /// <typeparam name="TSource2"></typeparam> 226 /// <typeparam name="TSource3"></typeparam> 227 /// <typeparam name="TSource4"></typeparam> 228 /// <param name="source1"></param> 229 /// <param name="source2"></param> 230 /// <param name="source3"></param> 231 /// <param name="source4"></param> 232 /// <param name="action"></param> 233 /// <returns></returns> 234 public static TValue AutoMapper<TValue, TSource1, TSource2, TSource3, TSource4>(TSource1 source1, TSource2 source2, TSource3 source3, TSource4 source4, Action<TValue> action) where TValue : class where TSource1 : class where TSource2 : class where TSource3 : class where TSource4 : class 235 { 236 TValue value = Activator.CreateInstance<TValue>(); 237 PropertyInfo[] propertyInfos = typeof(TValue).GetProperties(); 238 Dictionary<string, string> keys = new Dictionary<string, string>(); 239 value = SetValue(value, propertyInfos, Initdic(source1), keys); 240 value = SetValue(value, propertyInfos, Initdic(source2), keys); 241 value = SetValue(value, propertyInfos, Initdic(source3), keys); 242 value = SetValue(value, propertyInfos, Initdic(source4), keys); 243 action(value); 244 return value; 245 } 246 /// <summary> 247 /// AutoMapper with multitype properties. 248 /// </summary> 249 /// <typeparam name="TValue"></typeparam> 250 /// <typeparam name="TSource1"></typeparam> 251 /// <typeparam name="TSource2"></typeparam> 252 /// <typeparam name="TSource3"></typeparam> 253 /// <typeparam name="TSource4"></typeparam> 254 /// <typeparam name="TSource5"></typeparam> 255 /// <param name="source1"></param> 256 /// <param name="source2"></param> 257 /// <param name="source3"></param> 258 /// <param name="source4"></param> 259 /// <param name="source5"></param> 260 /// <returns></returns> 261 public static TValue AutoMapper<TValue, TSource1, TSource2, TSource3, TSource4, TSource5>(TSource1 source1, TSource2 source2, TSource3 source3, TSource4 source4, TSource5 source5) where TValue : class where TSource1 : class where TSource2 : class where TSource3 : class where TSource4 : class where TSource5 : class 262 { 263 TValue value = Activator.CreateInstance<TValue>(); 264 PropertyInfo[] propertyInfos = typeof(TValue).GetProperties(); 265 Dictionary<string, string> keys = new Dictionary<string, string>(); 266 value = SetValue(value, propertyInfos, Initdic(source1), keys); 267 value = SetValue(value, propertyInfos, Initdic(source2), keys); 268 value = SetValue(value, propertyInfos, Initdic(source3), keys); 269 value = SetValue(value, propertyInfos, Initdic(source4), keys); 270 value = SetValue(value, propertyInfos, Initdic(source5), keys); 271 return value; 272 } 273 /// <summary> 274 /// AutoMapper with multitype properties.Support for Use Action to custom special fields. 275 /// </summary> 276 /// <typeparam name="TValue"></typeparam> 277 /// <typeparam name="TSource1"></typeparam> 278 /// <typeparam name="TSource2"></typeparam> 279 /// <typeparam name="TSource3"></typeparam> 280 /// <typeparam name="TSource4"></typeparam> 281 /// <typeparam name="TSource5"></typeparam> 282 /// <param name="source1"></param> 283 /// <param name="source2"></param> 284 /// <param name="source3"></param> 285 /// <param name="source4"></param> 286 /// <param name="source5"></param> 287 /// <param name="action"></param> 288 /// <returns></returns> 289 public static TValue AutoMapper<TValue, TSource1, TSource2, TSource3, TSource4, TSource5>(TSource1 source1, TSource2 source2, TSource3 source3, TSource4 source4, TSource5 source5, Action<TValue> action) where TValue : class where TSource1 : class where TSource2 : class where TSource3 : class where TSource4 : class where TSource5 : class 290 { 291 TValue value = Activator.CreateInstance<TValue>(); 292 PropertyInfo[] propertyInfos = typeof(TValue).GetProperties(); 293 Dictionary<string, string> keys = new Dictionary<string, string>(); 294 value = SetValue(value, propertyInfos, Initdic(source1), keys); 295 value = SetValue(value, propertyInfos, Initdic(source2), keys); 296 value = SetValue(value, propertyInfos, Initdic(source3), keys); 297 value = SetValue(value, propertyInfos, Initdic(source4), keys); 298 value = SetValue(value, propertyInfos, Initdic(source5), keys); 299 action(value); 300 return value; 301 } 302 } 303 }
二、Expression Tree 表达式树版本
1 /********************************************************* 2 * CopyRight: 7TINY CODE BUILDER. 3 * Version: 5.0.0 4 * Author: 7tiny 5 * Address: Earth 6 * Create: 2018-03-16 10:11:43 7 * Modify: 2018-4-3 11:35:53 8 * E-mail: dong@7tiny.com | sevenTiny@foxmail.com 9 * GitHub: https://github.com/sevenTiny 10 * Personal web site: http://www.7tiny.com 11 * Technical WebSit: http://www.cnblogs.com/7tiny/ 12 * Description: 13 * Thx , Best Regards ~ 14 *********************************************************/ 15 using System; 16 using System.Collections.Generic; 17 using System.Linq; 18 using System.Linq.Expressions; 19 using System.Reflection; 20 21 namespace SevenTiny.Bantina.AutoMapper 22 { 23 public sealed class Mapper<TIn, TOut> where TOut : class where TIn : class 24 { 25 private Mapper() { } 26 private static readonly Func<TIn, TOut> funcCache = GetFunc(); 27 public static TOut AutoMapper(TIn tIn) 28 { 29 return funcCache(tIn); 30 } 31 public static TOut AutoMapper(TIn tIn, Action<TOut> action) 32 { 33 TOut outValue = funcCache(tIn); 34 action(outValue); 35 return outValue; 36 } 37 private static Func<TIn, TOut> GetFunc() 38 { 39 Type[] types = new Type[] { typeof(TIn) }; 40 MemberInitExpression memberInitExpression; 41 List<ParameterExpression> parameterExpressionList; 42 MapperExpressionCommon.GetFunc(typeof(TOut), types, out memberInitExpression, out parameterExpressionList); 43 Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, parameterExpressionList); 44 return lambda.Compile(); 45 } 46 } 47 public sealed class Mapper<TIn1, TIn2, TOut> where TOut : class where TIn1 : class where TIn2 : class 48 { 49 private Mapper() { } 50 public static TOut AutoMapper(TIn1 tIn1, TIn2 tIn2) 51 { 52 return funcCache(tIn1, tIn2); 53 } 54 public static TOut AutoMapper(TIn1 tIn1, TIn2 tIn2, Action<TOut> action) 55 { 56 TOut outValue = funcCache(tIn1, tIn2); 57 action(outValue); 58 return outValue; 59 } 60 private static readonly Func<TIn1, TIn2, TOut> funcCache = GetFunc(); 61 private static Func<TIn1, TIn2, TOut> GetFunc() 62 { 63 Type[] types = new Type[] { typeof(TIn1), typeof(TIn2) }; 64 MemberInitExpression memberInitExpression; 65 List<ParameterExpression> parameterExpressionList; 66 MapperExpressionCommon.GetFunc(typeof(TOut), types, out memberInitExpression, out parameterExpressionList); 67 Expression<Func<TIn1, TIn2, TOut>> lambda = Expression.Lambda<Func<TIn1, TIn2, TOut>>(memberInitExpression, parameterExpressionList); 68 return lambda.Compile(); 69 } 70 } 71 public sealed class Mapper<TIn1, TIn2, TIn3, TOut> where TOut : class where TIn1 : class where TIn2 : class where TIn3 : class 72 { 73 private Mapper() { } 74 public static TOut AutoMapper(TIn1 tIn1, TIn2 tIn2, TIn3 tIn3) 75 { 76 return funcCache(tIn1, tIn2, tIn3); 77 } 78 public static TOut AutoMapper(TIn1 tIn1, TIn2 tIn2, TIn3 tIn3, Action<TOut> action) 79 { 80 TOut outValue = funcCache(tIn1, tIn2, tIn3); 81 action(outValue); 82 return outValue; 83 } 84 private static readonly Func<TIn1, TIn2, TIn3, TOut> funcCache = GetFunc(); 85 private static Func<TIn1, TIn2, TIn3, TOut> GetFunc() 86 { 87 Type[] types = new Type[] { typeof(TIn1), typeof(TIn2), typeof(TIn3) }; 88 MemberInitExpression memberInitExpression; 89 List<ParameterExpression> parameterExpressionList; 90 MapperExpressionCommon.GetFunc(typeof(TOut), types, out memberInitExpression, out parameterExpressionList); 91 Expression<Func<TIn1, TIn2, TIn3, TOut>> lambda = Expression.Lambda<Func<TIn1, TIn2, TIn3, TOut>>(memberInitExpression, parameterExpressionList); 92 return lambda.Compile(); 93 } 94 } 95 public sealed class Mapper<TIn1, TIn2, TIn3, TIn4, TOut> where TOut : class where TIn1 : class where TIn2 : class where TIn3 : class where TIn4 : class 96 { 97 private Mapper() { } 98 public static TOut AutoMapper(TIn1 tIn1, TIn2 tIn2, TIn3 tIn3, TIn4 tIn4) 99 { 100 return funcCache(tIn1, tIn2, tIn3, tIn4); 101 } 102 public static TOut AutoMapper(TIn1 tIn1, TIn2 tIn2, TIn3 tIn3, TIn4 tIn4, Action<TOut> action) 103 { 104 TOut outValue = funcCache(tIn1, tIn2, tIn3, tIn4); 105 action(outValue); 106 return outValue; 107 } 108 private static readonly Func<TIn1, TIn2, TIn3, TIn4, TOut> funcCache = GetFunc(); 109 private static Func<TIn1, TIn2, TIn3, TIn4, TOut> GetFunc() 110 { 111 Type[] types = new Type[] { typeof(TIn1), typeof(TIn2), typeof(TIn3), typeof(TIn4) }; 112 MemberInitExpression memberInitExpression; 113 List<ParameterExpression> parameterExpressionList; 114 MapperExpressionCommon.GetFunc(typeof(TOut), types, out memberInitExpression, out parameterExpressionList); 115 Expression<Func<TIn1, TIn2, TIn3, TIn4, TOut>> lambda = Expression.Lambda<Func<TIn1, TIn2, TIn3, TIn4, TOut>>(memberInitExpression, parameterExpressionList); 116 return lambda.Compile(); 117 } 118 } 119 public sealed class Mapper<TIn1, TIn2, TIn3, TIn4, TIn5, TOut> where TOut : class where TIn1 : class where TIn2 : class where TIn3 : class where TIn4 : class where TIn5 : class 120 { 121 private Mapper() { } 122 public static TOut AutoMapper(TIn1 tIn1, TIn2 tIn2, TIn3 tIn3, TIn4 tIn4, TIn5 tIn5) 123 { 124 return funcCache(tIn1, tIn2, tIn3, tIn4, tIn5); 125 } 126 public static TOut AutoMapper(TIn1 tIn1, TIn2 tIn2, TIn3 tIn3, TIn4 tIn4, TIn5 tIn5, Action<TOut> action) 127 { 128 TOut outValue = funcCache(tIn1, tIn2, tIn3, tIn4, tIn5); 129 action(outValue); 130 return outValue; 131 } 132 private static readonly Func<TIn1, TIn2, TIn3, TIn4, TIn5, TOut> funcCache = GetFunc(); 133 private static Func<TIn1, TIn2, TIn3, TIn4, TIn5, TOut> GetFunc() 134 { 135 Type[] types = new Type[] { typeof(TIn1), typeof(TIn2), typeof(TIn3), typeof(TIn4), typeof(TIn5) }; 136 MemberInitExpression memberInitExpression; 137 List<ParameterExpression> parameterExpressionList; 138 MapperExpressionCommon.GetFunc(typeof(TOut), types, out memberInitExpression, out parameterExpressionList); 139 Expression<Func<TIn1, TIn2, TIn3, TIn4, TIn5, TOut>> lambda = Expression.Lambda<Func<TIn1, TIn2, TIn3, TIn4, TIn5, TOut>>(memberInitExpression, parameterExpressionList); 140 return lambda.Compile(); 141 } 142 } 143 }