C# 高性能对象映射(表达式树实现)
前言
上篇简单实现了对象映射,针对数组,集合,嵌套类并没有给出实现,这一篇继续完善细节。
开源对象映射类库映射分析
1.AutoMapper
实现原理:主要通过表达式树Api 实现对象映射
优点: .net功能最全的对象映射类库。
缺点:当出现复杂类型和嵌套类型时性能直线下降,甚至不如序列化快
2.TinyMapper
实现原理:主要通过Emit 实现对象映射
优点:速度非常快。在处理复杂类型和嵌套类型性能也很好
缺点:相对AutoMapper功能上少一些,Emit的实现方案,在代码阅读和调试上相对比较麻烦,而表达式树直接观察 DebugView中生成的代码结构便可知道问题所在
3. 本文的对象映射库
针对AutoMapper 处理复杂类型和嵌套类型时性能非常差的情况,自己实现一个表达式树版的高性能方案
此篇记录下实现对象映射库的过程
构造测试类
1 public class TestA 2 { 3 public int Id { get; set; } 4 public string Name { get; set; } 5 6 public TestC TestClass { get; set; } 7 8 public IEnumerable<TestC> TestLists { get; set; } 9 } 10 11 public class TestB 12 { 13 public int Id { get; set; } 14 public string Name { get; set; } 15 16 public TestD TestClass { get; set; } 17 18 public TestD[] TestLists { get; set; } 19 } 20 21 public class TestC 22 { 23 public int Id { get; set; } 24 public string Name { get; set; } 25 26 public TestC SelfClass { get; set; } 27 } 28 29 public class TestD 30 { 31 public int Id { get; set; } 32 public string Name { get; set; } 33 34 public TestD SelfClass { get; set; } 35 }
1.初步实现
利用表达式树给属性赋值 利用 Expresstion.New构造 var b=new B{};
1 private static Func<TSource, TTarget> GetMap<TSource, TTarget>() 2 { 3 var sourceType = typeof(TSource); 4 var targetType = typeof(TTarget); 5 6 //构造 p=> 7 var parameterExpression = Expression.Parameter(sourceType, "p"); 8 9 //构造 p=>new TTarget{ Id=p.Id,Name=p.Name }; 10 var memberBindingList = new List<MemberBinding>(); 11 foreach (var sourceItem in sourceType.GetProperties()) 12 { 13 var targetItem = targetType.GetProperty(sourceItem.Name); 14 if (targetItem == null || sourceItem.PropertyType != targetItem.PropertyType) 15 continue; 16 17 var property = Expression.Property(parameterExpression, sourceItem); 18 var memberBinding = Expression.Bind(targetItem, property); 19 memberBindingList.Add(memberBinding); 20 } 21 var memberInitExpression = Expression.MemberInit(Expression.New(targetType), memberBindingList); 22 23 var lambda = Expression.Lambda<Func<TSource, TTarget>>(memberInitExpression, parameterExpression ); 24 25 Console.WriteLine(lambda); 26 return lambda.Compile(); 27 }
调用如下
14 15 class Program 16 { 17 static void Main(string[] args) 18 { 19 var testA = new TestA { Id = 1, Name = "张三" }; 20 var func = Map<TestA, TestB>(); 21 TestB testB = func(testA); 22 Console.WriteLine($"testB.Id={testB.Id},testB.Name={testB.Name}"); 23 Console.ReadLine(); 24 } 25 }
输出结果
总结:此方法需要调用前需要手动编译下,然后再调用委托没有缓存委托,相对麻烦。
2.缓存实现
利用静态泛型类缓存泛型委托
1 public class DataMapper<TSource, TTarget> 2 { 3 private static Func<TSource, TTarget> MapFunc { get; set; } 4 5 public static TTarget Map(TSource source) 6 { 7 if (MapFunc == null) 8 MapFunc = GetMap();//方法在上边 9 return MapFunc(source); 10 }11 }
调用方法
1 static void Main(string[] args) 2 { 3 var testA = new TestA { Id = 1, Name = "张三" }; 4 TestB testB = DataMapper<TestA, TestB>.Map(testA);//委托不存在时自动生成,存在时调用静态缓存 5 6 Console.WriteLine($"testB.Id={testB.Id},testB.Name={testB.Name}"); 7 Console.ReadLine(); 8 }
输出结果
总结:引入静态泛型类能解决泛型委托缓存提高性能,但是有两个问题 1.当传入参数为null时 则会抛出空引用异常 2.出现复杂类型上述方法便不能满足了
3.解决参数为空值和复杂类型的问题
首先先用常规代码实现下带有复杂类型赋值的情况
1 public TestB GetTestB(TestA testA) 2 { 3 TestB testB; 4 if (testA != null) 5 { 6 testB = new TestB(); 7 testB.Id = testA.Id; 8 testB.Name = testA.Name; 9 if (testA.TestClass != null) 10 { 11 testB.TestClass = new TestD(); 12 testB.TestClass.Id = testA.TestClass.Id; 13 testB.TestClass.Name = testA.TestClass.Name; 14 } 15 } 16 else 17 { 18 testB = null; 19 } 20 return testB; 21 }
将上面的代码翻译成表达式树
1 private static Func<TSource, TTarget> GetMap() 2 { 3 var sourceType = typeof(TSource); 4 var targetType = typeof(TTarget); 5 6 //Func委托传入变量 7 var parameter = Expression.Parameter(sourceType); 8 9 //声明一个返回值变量 10 var variable = Expression.Variable(targetType); 11 //创建一个if条件表达式 12 var test = Expression.NotEqual(parameter, Expression.Constant(null, sourceType));// p==null; 13 var ifTrue = Expression.Block(GetExpression(parameter, variable, sourceType, targetType)); 14 var IfThen = Expression.IfThen(test, ifTrue); 15 16 //构造代码块 17 var block = Expression.Block(new[] { variable }, parameter, IfThen, variable); 18 19 var lambda = Expression.Lambda<Func<TSource, TTarget>>(block, parameter); 20 return lambda.Compile(); 21 } 22 23 private static List<Expression> GetExpression(Expression parameter, Expression variable, Type sourceType, Type targetType) 24 { 25 //创建一个表达式集合 26 var expressions = new List<Expression>(); 27 28 expressions.Add(Expression.Assign(variable, Expression.MemberInit(Expression.New(targetType)))); 29 30 foreach (var targetItem in targetType.GetProperties().Where(x => x.PropertyType.IsPublic && x.CanWrite)) 31 { 32 var sourceItem = sourceType.GetProperty(targetItem.Name); 33 34 //判断实体的读写权限 35 if (sourceItem == null || !sourceItem.CanRead || sourceItem.PropertyType.IsNotPublic) 36 continue; 37 38 var sourceProperty = Expression.Property(parameter, sourceItem); 39 var targetProperty = Expression.Property(variable, targetItem); 40 41 //判断都是class 且类型不相同时 42 if (targetItem.PropertyType.IsClass && sourceItem.PropertyType.IsClass && targetItem.PropertyType != sourceItem.PropertyType) 43 { 44 if (targetItem.PropertyType != targetType)//不处理嵌套循环的情况 45 { 46 //由于类型是class 所以默认值是null 47 var testItem = Expression.NotEqual(sourceProperty, Expression.Constant(null, sourceItem.PropertyType)); 48 49 var itemExpressions = GetExpression(sourceProperty, targetProperty, sourceItem.PropertyType, targetItem.PropertyType); 50 var ifTrueItem = Expression.Block(itemExpressions); 51 52 var IfThenItem = Expression.IfThen(testItem, ifTrueItem); 53 expressions.Add(IfThenItem); 54 55 continue; 56 } 57 } 58 59 //目标值类型时 且两者类型不一致时跳过 60 if (targetItem.PropertyType != sourceItem.PropertyType) 61 continue; 62 63 expressions.Add(Expression.Assign(targetProperty, sourceProperty)); 64 } 65 66 return expressions; 67 }
总结:此方案,运用 Expression.IfThen(testItem, ifTrueItem) 判断空值问题,通过递归调用 GetExpression()方法,处理复杂类型。 但是。。。针对嵌套类仍然不能解决。因为表达式树是在实际调用方法之前就生成的,在没有实际的
参数值传入之前,生成的表达式是不知道有多少层级的。有个比较low的方案是,预先设定嵌套层级为10层,然后生成一个有10层 if(P!=null) 的判断。如果传入的参数层级超过10层了呢,就得手动调整生成的树,此方案也否决。
最后得出的结论只能在表达式中动态调用方法。
4.最终版本
通过动态调用方法解决嵌套类,代码如下
using static System.Linq.Expressions.Expression; public static class Mapper<TSource, TTarget> where TSource : class where TTarget : class { public readonly static Func<TSource, TTarget> MapFunc = GetMapFunc(); public readonly static Action<TSource, TTarget> MapAction = GetMapAction(); /// <summary> /// 将对象TSource转换为TTarget /// </summary> /// <param name="source"></param> /// <returns></returns> public static TTarget Map(TSource source) => MapFunc(source); public static List<TTarget> MapList(IEnumerable<TSource> sources)=> sources.Select(MapFunc).ToList(); /// <summary> /// 将对象TSource的值赋给给TTarget /// </summary> /// <param name="source"></param> /// <param name="target"></param> public static void Map(TSource source, TTarget target) => MapAction(source, target); private static Func<TSource, TTarget> GetMapFunc() { var sourceType = typeof(TSource); var targetType = typeof(TTarget); //Func委托传入变量 var parameter = Parameter(sourceType, "p"); var memberBindings = new List<MemberBinding>(); var targetTypes = targetType.GetProperties().Where(x => x.PropertyType.IsPublic && x.CanWrite); foreach (var targetItem in targetTypes) { var sourceItem = sourceType.GetProperty(targetItem.Name); //判断实体的读写权限 if (sourceItem == null || !sourceItem.CanRead || sourceItem.PropertyType.IsNotPublic) continue; //标注NotMapped特性的属性忽略转换 if (sourceItem.GetCustomAttribute<NotMappedAttribute>() != null) continue; var sourceProperty = Property(parameter, sourceItem); //当非值类型且类型不相同时 if (!sourceItem.PropertyType.IsValueType && sourceItem.PropertyType != targetItem.PropertyType) { //判断都是(非泛型)class if (sourceItem.PropertyType.IsClass && targetItem.PropertyType.IsClass && !sourceItem.PropertyType.IsGenericType && !targetItem.PropertyType.IsGenericType) { var expression = GetClassExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType); memberBindings.Add(Bind(targetItem, expression)); } //集合数组类型的转换 if (typeof(IEnumerable).IsAssignableFrom(sourceItem.PropertyType) && typeof(IEnumerable).IsAssignableFrom(targetItem.PropertyType)) { var expression = GetListExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType); memberBindings.Add(Bind(targetItem, expression)); } continue; } if (targetItem.PropertyType != sourceItem.PropertyType) continue; memberBindings.Add(Bind(targetItem, sourceProperty)); } //创建一个if条件表达式 var test = NotEqual(parameter, Constant(null, sourceType));// p==null; var ifTrue = MemberInit(New(targetType), memberBindings); var condition = Condition(test, ifTrue, Constant(null, targetType)); var lambda = Lambda<Func<TSource, TTarget>>(condition, parameter); return lambda.Compile(); } /// <summary> /// 类型是clas时赋值 /// </summary> /// <param name="sourceProperty"></param> /// <param name="targetProperty"></param> /// <param name="sourceType"></param> /// <param name="targetType"></param> /// <returns></returns> private static Expression GetClassExpression(Expression sourceProperty, Type sourceType, Type targetType) { //条件p.Item!=null var testItem = NotEqual(sourceProperty, Constant(null, sourceType)); //构造回调 Mapper<TSource, TTarget>.Map() var mapperType = typeof(Mapper<,>).MakeGenericType(sourceType, targetType); var iftrue = Call(mapperType.GetMethod(nameof(Map), new[] { sourceType }), sourceProperty); var conditionItem = Condition(testItem, iftrue, Constant(null, targetType)); return conditionItem; } /// <summary> /// 类型为集合时赋值 /// </summary> /// <param name="sourceProperty"></param> /// <param name="targetProperty"></param> /// <param name="sourceType"></param> /// <param name="targetType"></param> /// <returns></returns> private static Expression GetListExpression(Expression sourceProperty, Type sourceType, Type targetType) { //条件p.Item!=null var testItem = NotEqual(sourceProperty, Constant(null, sourceType)); //构造回调 Mapper<TSource, TTarget>.MapList() var sourceArg = sourceType.IsArray ? sourceType.GetElementType() : sourceType.GetGenericArguments()[0]; var targetArg = targetType.IsArray ? targetType.GetElementType() : targetType.GetGenericArguments()[0]; var mapperType = typeof(Mapper<,>).MakeGenericType(sourceArg, targetArg); var mapperExecMap = Call(mapperType.GetMethod(nameof(MapList), new[] { sourceType }), sourceProperty); Expression iftrue; if (targetType == mapperExecMap.Type) { iftrue = mapperExecMap; } else if (targetType.IsArray)//数组类型调用ToArray()方法 { iftrue = Call(mapperExecMap, mapperExecMap.Type.GetMethod("ToArray")); } else if (typeof(IDictionary).IsAssignableFrom(targetType)) { iftrue = Constant(null, targetType);//字典类型不转换 } else { iftrue = Convert(mapperExecMap, targetType); } var conditionItem = Condition(testItem, iftrue, Constant(null, targetType)); return conditionItem; } private static Action<TSource, TTarget> GetMapAction() { var sourceType = typeof(TSource); var targetType = typeof(TTarget); //Func委托传入变量 var sourceParameter = Parameter(sourceType, "p"); var targetParameter = Parameter(targetType, "t"); //创建一个表达式集合 var expressions = new List<Expression>(); var targetTypes = targetType.GetProperties().Where(x => x.PropertyType.IsPublic && x.CanWrite); foreach (var targetItem in targetTypes) { var sourceItem = sourceType.GetProperty(targetItem.Name); //判断实体的读写权限 if (sourceItem == null || !sourceItem.CanRead || sourceItem.PropertyType.IsNotPublic) continue; //标注NotMapped特性的属性忽略转换 if (sourceItem.GetCustomAttribute<NotMappedAttribute>() != null) continue; var sourceProperty = Property(sourceParameter, sourceItem); var targetProperty = Property(targetParameter, targetItem); //当非值类型且类型不相同时 if (!sourceItem.PropertyType.IsValueType && sourceItem.PropertyType != targetItem.PropertyType) { //判断都是(非泛型)class if (sourceItem.PropertyType.IsClass && targetItem.PropertyType.IsClass && !sourceItem.PropertyType.IsGenericType && !targetItem.PropertyType.IsGenericType) { var expression = GetClassExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType); expressions.Add(Assign(targetProperty, expression)); } //集合数组类型的转换 if (typeof(IEnumerable).IsAssignableFrom(sourceItem.PropertyType) && typeof(IEnumerable).IsAssignableFrom(targetItem.PropertyType)) { var expression = GetListExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType); expressions.Add(Assign(targetProperty, expression)); } continue; } if (targetItem.PropertyType != sourceItem.PropertyType) continue; expressions.Add(Assign(targetProperty, sourceProperty)); } //当Target!=null判断source是否为空 var testSource = NotEqual(sourceParameter, Constant(null, sourceType)); var ifTrueSource = Block(expressions); var conditionSource = IfThen(testSource, ifTrueSource); //判断target是否为空 var testTarget = NotEqual(targetParameter, Constant(null, targetType)); var conditionTarget = IfThen(testTarget, conditionSource); var lambda = Lambda<Action<TSource, TTarget>>(conditionTarget, sourceParameter, targetParameter); return lambda.Compile(); } }
输出的 表达式
格式化后
1 p => IIF((p != null), 2 new TestB() 3 { 4 Id = p.Id, 5 Name = p.Name, 6 TestClass = IIF( 7 (p.TestClass != null), 8 Map(p.TestClass), 9 null 10 ), 11 TestLists = IIF( 12 (p.TestLists != null), 13 MapList(p.TestLists).ToArray(), 14 null 15 ) 16 }, 17 null)
说明 Map(p.TestClass) MapList(p.TestLists).ToArray(), 完整的信息为 Mapper<TestC,TestD>.Map() Mapper<TestC,TestD>.MapList()
总结:解决嵌套类的核心代码
101 //构造回调 Mapper<TSource, TTarget>.Map()
102 var mapperType = typeof(DataMapper<,>).MakeGenericType(sourceType, targetType);
103 var mapperExecMap = Expression.Call(mapperType.GetMethod(nameof(Map), new[] { sourceType }), sourceProperty);
利用Expression.Call 根据参数类型动态生成 对象映射的表达式
性能测试
写了这么多最终目的还是为了解决性能问题,下面将对比下性能
1.测试类
1 public static class MapperTest 2 { 3 //执行次数 4 public static int Count = 100000; 5 6 //简单类型 7 public static void Nomal() 8 { 9 Console.WriteLine($"******************简单类型:{Count / 10000}万次执行时间*****************"); 10 var model = new TestA 11 { 12 Id =1, 13 Name = "张三", 14 }; 15 16 //计时 17 var sw = Stopwatch.StartNew(); 18 for (int i = 0; i < Count; i++) 19 { 20 if (model != null) 21 { 22 var b = new TestB 23 { 24 Id = model.Id, 25 Name = model.Name, 26 }; 27 } 28 } 29 sw.Stop(); 30 Console.WriteLine($"原生的时间:{sw.ElapsedMilliseconds}ms"); 31 32 Exec(model); 33 } 34 35 //复杂类型 36 public static void Complex() 37 { 38 Console.WriteLine($"********************复杂类型:{Count / 10000}万次执行时间*********************"); 39 var model = new TestA 40 { 41 Id = 1, 42 Name = "张三", 43 TestClass = new TestC 44 { 45 Id = 2, 46 Name = "lisi", 47 }, 48 }; 49 50 //计时 51 var sw = Stopwatch.StartNew(); 52 for (int i = 0; i < Count; i++) 53 { 54 55 if (model != null) 56 { 57 var b = new TestB 58 { 59 Id = model.Id, 60 Name = model.Name, 61 }; 62 if (model.TestClass != null) 63 { 64 b.TestClass = new TestD 65 { 66 Id = i, 67 Name = "lisi", 68 }; 69 } 70 } 71 } 72 sw.Stop(); 73 Console.WriteLine($"原生的时间:{sw.ElapsedMilliseconds}ms"); 74 Exec(model); 75 } 76 77 //嵌套类型 78 public static void Nest() 79 { 80 Console.WriteLine($"*****************嵌套类型:{Count / 10000}万次执行时间*************************"); 81 var model = new TestA 82 { 83 Id = 1, 84 Name = "张三", 85 TestClass = new TestC 86 { 87 Id = 1, 88 Name = "lisi", 89 SelfClass = new TestC 90 { 91 Id = 2, 92 Name = "lisi", 93 SelfClass = new TestC 94 { 95 Id = 3, 96 Name = "lisi", 97 SelfClass = new TestC 98 { 99 Id = 4, 100 Name = "lisi", 101 }, 102 }, 103 }, 104 }, 105 }; 106 //计时 107 var item = model; 108 var sw = Stopwatch.StartNew(); 109 for (int i = 0; i < Count; i++) 110 { 111 //这里每一步需要做非空判断的,书写太麻烦省去了 112 if (model != null) 113 { 114 var b = new TestB 115 { 116 Id = model.Id, 117 Name = model.Name, 118 TestClass = new TestD 119 { 120 Id = model.TestClass.Id, 121 Name = model.TestClass.Name, 122 SelfClass = new TestD 123 { 124 Id = model.TestClass.SelfClass.Id, 125 Name = model.TestClass.SelfClass.Name, 126 SelfClass = new TestD 127 { 128 Id = model.TestClass.SelfClass.SelfClass.Id, 129 Name = model.TestClass.SelfClass.SelfClass.Name, 130 SelfClass = new TestD 131 { 132 Id = model.TestClass.SelfClass.SelfClass.SelfClass.Id, 133 Name = model.TestClass.SelfClass.SelfClass.SelfClass.Name, 134 }, 135 }, 136 }, 137 }, 138 }; 139 } 140 } 141 sw.Stop(); 142 Console.WriteLine($"原生的时间:{sw.ElapsedMilliseconds}ms"); 143 144 Exec(model); 145 } 146 147 //集合 148 public static void List() 149 { 150 Console.WriteLine($"********************集合类型:{Count/10000}万次执行时间***************************"); 151 152 var model = new TestA 153 { 154 Id = 1, 155 Name = "张三", 156 TestLists = new List<TestC> { 157 new TestC{ 158 Id = 1, 159 Name = "张三", 160 }, 161 new TestC{ 162 Id = -1, 163 Name = "张三", 164 }, 165 } 166 }; 167 168 169 //计时 170 var sw = Stopwatch.StartNew(); 171 for (int i = 0; i < Count; i++) 172 { 173 var item = model; 174 if (item != null) 175 { 176 var b = new TestB 177 { 178 Id = item.Id, 179 Name = item.Name, 180 TestLists = new List<TestD> { 181 new TestD{ 182 Id = item.Id, 183 Name = item.Name, 184 }, 185 new TestD{ 186 Id = -item.Id, 187 Name = item.Name, 188 }, 189 }.ToArray() 190 }; 191 } 192 } 193 sw.Stop(); 194 Console.WriteLine($"原生的时间:{sw.ElapsedMilliseconds}ms"); 195 196 Exec(model); 197 } 198 199 public static void Exec(TestA model) 200 { 201 //表达式 202 Mapper<TestA, TestB>.Map(model); 203 var sw = Stopwatch.StartNew(); 204 for (int i = 0; i < Count; i++) 205 { 206 var b = Mapper<TestA, TestB>.Map(model); 207 } 208 sw.Stop(); 209 Console.WriteLine($"表达式的时间:{sw.ElapsedMilliseconds}ms"); 210 211 //AutoMapper 212 sw.Restart(); 213 for (int i = 0; i < Count; i++) 214 { 215 var b = AutoMapper.Mapper.Map<TestA, TestB>(model); 216 } 217 sw.Stop(); 218 Console.WriteLine($"AutoMapper时间:{sw.ElapsedMilliseconds}ms"); 219 220 //TinyMapper 221 sw.Restart(); 222 for (int i = 0; i < Count; i++) 223 { 224 var b = TinyMapper.Map<TestA, TestB>(model); 225 } 226 sw.Stop(); 227 Console.WriteLine($"TinyMapper时间:{sw.ElapsedMilliseconds}ms"); 228 } 229 } 230 231
2.调用测试
1 static void Main(string[] args) 2 { 3 AutoMapper.Mapper.Initialize(cfg => cfg.CreateMap<TestA, TestB>()); 4 TinyMapper.Bind<TestA, TestB>(); 5 Mapper<TestA, TestB>.Map(new TestA()); 6 7 8 MapperTest.Count = 10000; 9 MapperTest.Nomal(); 10 MapperTest.Complex(); 11 MapperTest.Nest(); 12 MapperTest.List(); 13 14 MapperTest.Count = 100000; 15 MapperTest.Nomal(); 16 MapperTest.Complex(); 17 MapperTest.Nest(); 18 MapperTest.List(); 19 20 MapperTest.Count = 1000000; 21 MapperTest.Nomal(); 22 MapperTest.Complex(); 23 MapperTest.Nest(); 24 MapperTest.List(); 25 26 MapperTest.Count = 10000000; 27 MapperTest.Nomal(); 28 MapperTest.Complex(); 29 MapperTest.Nest(); 30 MapperTest.List(); 31 32 33 Console.WriteLine($"------------结束--------------------"); 34 Console.ReadLine(); 35 }
3.结果
1万次
10万次
100万次
1000万次
上图结果AutoMapper 在非简单类型的转换上比其他方案有50倍以上的差距,几乎就跟反射的结果一样。
作者:costyuan
GitHub地址:https://github.com/bieyuan/.net-core-DTO
地址:http://www.cnblogs.com/castyuan/p/9324088.html
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
如果文中有什么错误,欢迎指出,谢谢!