最近粗淺的學習了下AutoMapper 這個做對象映射的第三方工具,覺得非常方便使用,所以簡單的總結了一下我能想到的簡單的對象映射的方式。 占時先不考慮源對象成員到目標對象成員的指定映射(即成員名不一致),先準備好兩個類Students-StudentsDto;Teachers-TeachersDt ...
最近粗淺的學習了下AutoMapper 這個做對象映射的第三方工具,覺得非常方便使用,所以簡單的總結了一下我能想到的簡單的對象映射的方式。
占時先不考慮源對象成員到目標對象成員的指定映射(即成員名不一致),先準備好兩個類Students-StudentsDto;Teachers-TeachersDto
1 public class Students 2 { 3 public int No { get; set; } 4 public string Name { get; set; } 5 public bool Gender { get; set; } 6 public string Class { get; set; } 7 8 public string _remark; 9 } 10 11 public class StudentsDto 12 { 13 public int No { get; set; } 14 public string Name { get; set; } 15 public bool Gender { get; set; } 16 public string Class { get; set; } 17 18 public string _remark; 19 } 20 21 public class Teachers 22 { 23 public int No { get; set; } 24 25 public string Course { get; set; } 26 27 public string Name { get; set; } 28 } 29 30 public class TeachersDto 31 { 32 public int No { get; set; } 33 34 public string Course { get; set; } 35 36 public string Name { get; set; } 37 }
我們先使用普通的對象裝載方式:
1 StudentsDto studentsDto = new StudentsDto { No = 1, Name = "Epic", Gender = true, Class = "家裡蹲一班", _remark = "逗比" }; 2 TeachersDto teachersDto = new TeachersDto { No = 2, Name = "Eleven", Course = ".net" }; 3 Students students = new Students 4 { 5 No = studentsDto.No, 6 Name = studentsDto.Name, 7 Gender = studentsDto.Gender, 8 Class = studentsDto.Class, 9 _remark = studentsDto._remark 10 }; 11 Teachers teachers = new Teachers 12 { 13 No = teachersDto.No, 14 Name = teachersDto.Name, 15 Course = teachersDto.Course 16 };
總結:其思路無非就是先new一個對象實例,然後將該實例的成員一一賦值。
1.通過反射的方式來實現對象映射,是我們最容易想到的方式,思路也很簡單
1 public static TModel Trans<TModel, TModelDto>(TModelDto dto) 2 where TModel : class 3 where TModelDto : class 4 { 5 TModel model = Activator.CreateInstance(typeof(TModel)) as TModel; 6 //獲取TModel的屬性集合 7 PropertyInfo[] modlePropertys = typeof(TModel).GetProperties(); 8 //獲取TModelDto的屬性集合 9 Type type = dto.GetType(); 10 PropertyInfo[] propertys = type.GetProperties(); 11 foreach (var property in propertys) 12 { 13 foreach (var mproperty in modlePropertys) 14 { 15 //如果屬性名稱一致,則將該屬性值賦值到TModel實例中 16 //這裡可以用Attribute來實現成員的自定義映射 17 if (property.Name.Equals(mproperty.Name)) 18 { 19 mproperty.SetValue(model, property.GetValue(dto)); 20 break; 21 } 22 } 23 } 24 25 //獲取TModel的欄位集合 26 FieldInfo[] modelfieldInfos = typeof(TModel).GetFields(); 27 //獲取TModelDto的欄位集合 28 FieldInfo[] fieldInfos = type.GetFields(); 29 foreach (var field in fieldInfos) 30 { 31 foreach (var mfield in modelfieldInfos) 32 { 33 //如果欄位名稱一致,則將該欄位值賦值到TModel實例中 34 if (field.Name.Equals(mfield.Name)) 35 { 36 mfield.SetValue(model, field.GetValue(dto)); 37 break; 38 } 39 } 40 } 41 return model; 42 }
總結:通過反射來創建對象實例,然後將實例成語的值通過反射的方式獲取並賦值。
2.通過序列號的方式,對象類型可以轉換成json字元串,然後再由json字元串轉換成所需的對象不就可以了麽
1 public static TModel Trans<TModel, TModelDto>(TModelDto dto) 2 where TModel : class 3 where TModelDto : class 4 { 5 return JsonConvert.DeserializeObject<TModel>(JsonConvert.SerializeObject(dto)); 6 }
總結:通過序列號然後反序列化,這樣使用感覺並不是Newtonsoft.Json的初衷,不知道性能到底如何呢?
3.使用Expression表達式的方式來解決,將所需實例對象new、賦值的過程先寫入表達式,然後生成lambda表達式,最後編譯該表達式生成委托,invoke即可
1 public static class ExpressionAndSeesionMethod 2 3 { 4 public static Dictionary<string, object> _dictionary = new Dictionary<string, object>(); 5 6 public static TModel Trans<TModel, TModelDto>(TModelDto dto) 7 { 8 Type modelType = typeof(TModel); 9 Type modelDtoType = typeof(TModelDto); 10 11 //如果_dictionary中不存在該key,則存進去 12 string key = $"{modelDtoType.Name}-->{modelType.Name}"; 13 if (!_dictionary.ContainsKey(key)) 14 { 15 //創建一個lambda參數x,定義的對象為TModelDto 16 ParameterExpression parameterExpression = Expression.Parameter(modelDtoType, "x"); 17 //開始生成lambda表達式 18 List<MemberBinding> list = new List<MemberBinding>(); 19 foreach (var item in modelType.GetProperties()) 20 { 21 //為x參數表達式生成一個屬性值 22 MemberExpression property = Expression.Property(parameterExpression, modelDtoType.GetProperty(item.Name)); 23 //將該屬性初始化 eg:No=x.No 24 MemberBinding memberBinding = Expression.Bind(item, property); 25 list.Add(memberBinding); 26 } 27 28 foreach (var item in typeof(TModel).GetFields()) 29 { 30 //為x參數表達式生成一個欄位值 31 MemberExpression field = Expression.Field(parameterExpression, modelDtoType.GetField(item.Name)); 32 //將該欄位初始化 33 MemberBinding memberBinding = Expression.Bind(item, field); 34 list.Add(memberBinding); 35 } 36 //調用構造函數,初始化一個TModel eg: new{No=x.No...} 37 MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(modelType), list); 38 //創建lambda表達式 eg: x=>new{ No=x.No...} 39 Expression<Func<TModelDto, TModel>> lambda = Expression.Lambda<Func<TModelDto, TModel>>(memberInitExpression, parameterExpression); 40 //將lambda表達式生成委托 41 Func<TModelDto, TModel> func = lambda.Compile(); 42 _dictionary[key] = func; 43 } 44 return ((Func<TModelDto, TModel>)_dictionary[key]).Invoke(dto); 45 } 46 }
總結:使用表達式樹的方式,可以將生成的委托保存起來,這裡使用dictionary字典,在需要使用的時候調用即可(一次生成委托,後續可多次使用);既然是多個委托,那是不是可以使用的泛型委托Func<TIn,TResult>呢?
使用泛型緩存:
1 /// <summary> 2 ///泛型委托基於泛型類之上 3 ///泛型靜態類在確定參數類型的時候會調用其靜態函數 4 ///在執行委托時,泛型委托會內置查找相應的委托來執行 5 /// </summary> 6 public static class ExpressionAndFuncMethod<TModel, TModelDto> 7 where TModel : class 8 where TModelDto : class 9 { 10 static ExpressionAndFuncMethod() 11 { 12 ExpressionMapper(); 13 } 14 15 public static Func<TModelDto, TModel> _func = null; 16 17 public static void ExpressionMapper() 18 { 19 Type modelType = typeof(TModel); 20 Type modelDtoType = typeof(TModelDto); 21 22 //創建一個lambda參數x,定義的對象為TModelDto 23 ParameterExpression parameterExpression = Expression.Parameter(modelDtoType, "x"); 24 //開始生成lambda表達式 25 List<MemberBinding> list = new List<MemberBinding>(); 26 foreach (var item in modelType.GetProperties()) 27 { 28 //為x參數表達式生成一個屬性值 29 MemberExpression property = Expression.Property(parameterExpression, modelDtoType.GetProperty(item.Name)); 30 //將該屬性初始化 eg:No=x.No 31 MemberBinding memberBinding = Expression.Bind(item, property); 32 list.Add(memberBinding); 33 } 34 35 foreach (var item in typeof(TModel).GetFields()) 36 { 37 //為x參數表達式生成一個欄位值 38 MemberExpression field = Expression.Field(parameterExpression, modelDtoType.GetField(item.Name)); 39 //將該欄位初始化 40 MemberBinding memberBinding = Expression.Bind(item, field); 41 list.Add(memberBinding); 42 } 43 //調用構造函數,初始化一個TModel eg: new{No=x.No...} 44 MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(modelType), list); 45 //創建lambda表達式 eg: x=>new{ No=x.No...} 46 Expression<Func<TModelDto, TModel>> lambda = Expression.Lambda<Func<TModelDto, TModel>>(memberInitExpression, parameterExpression); 47 //將lambda表達式生成委托 48 _func = lambda.Compile(); 49 } 50 51 public static TModel Trans(TModelDto dto) 52 { 53 if (_func != null) 54 return _func(dto); 55 return default(TModel); 56 } 57 }
總結:使用泛型委托時,即用到了其泛型緩存,在調用指定參數的委托時,能快速查找並調用(這個性能應該是高於使用字典的查找)
4.使用AutoMapper
1 public static class AutoMapperMethod 2 { 3 /// <summary> 4 /// AutoMapper 必須先創建映射 5 /// </summary> 6 /// <param name="dictionary"></param> 7 public static void Init(Dictionary<Type, Type> dictionary) 8 { 9 AutoMapper.Mapper.Initialize(x => 10 { 11 foreach (var item in dictionary) 12 { 13 x.CreateMap(item.Key, item.Value); 14 } 15 }); 16 } 17 18 19 public static TModel Trans<TModel, TModelDto>(TModelDto dto) 20 where TModel : class 21 where TModelDto : class 22 { 23 return AutoMapper.Mapper.Map<TModelDto, TModel>(dto); 24 } 25 }
總結:AutoMapper先創建再調用的原則,非常適合core項目的 註冊-調用 思想,在Configure中進行註冊,然後使用時Map即可,AutoMap使用emit代碼開發(不太明白),性能很好,是現在最流行的映射工具。
最後,來看一下以上幾種方式的性能對比吧,測試條件是將StudentsDto實例轉換成Students實例,將TeachersDto實例轉換成Teachers實例,各轉換50萬次,耗時如下圖:
從上往下依次是:普通類型裝載、反射、序列化、表達式緩存、表達式泛型緩存、AutoMapper
由此可見,反射和序列化是比較慢的,表達式和AutoMapper的表現差不多,一般項目中,類型映射的次數也不會很大,使用AutoMapper就已經非常夠用了。
本人不才,還希望園內技術牛人多多指正。