前言
上篇簡單實現了對象映射,針對數組,集合,嵌套類並沒有給出實現,這一篇繼續完善細節。
開源對象映射類庫映射分析
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
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
如果文中有什么錯誤,歡迎指出,謝謝!