来源: 关于对象映射(Dto->model) 思路的一些想法 – EpicOfGilgamesh – 博客园
最近粗浅的学习了下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就已经非常够用了。
本人不才,还希望园内技术牛人多多指正。
Mikel
