接手的项目还在用 TinyMapper 的一个早期版本用来做自动映射工具,TinyMapper 虽然速度快,但在配置里不能转换类型,比如 deleted 在数据库中用 0、1 表示,转换成实体模型时没法转换成 bool 类型,就为了这一个属性,就必须手写代码人工转换(怪不得有些 Mapper 作者认为 TinyMapper 是一个 toy)。
于是试一试 AutoMapper,可是这货需要提前注册所有的映射关系,程序员本来就已经很累了。。。(最新版 TinyMapper 也要求提前注册所有映射关系)。
找出以前使用过的 ValueInjecter,可扩展性很强,使用反射来实现。虽然我认为对现在处理器性能而言,快慢已经不太重要了,但它速度实在太慢了,有些测试项目消耗时间是 json反序列化的一半,TinyMapper 和 AutoMapper 均使用 emit 实现,非常接近手写代码的速度了。
在 nuget.org 上找了找,还发现两个非常不错的 mapper:
1. UltraMapper 不需要提前注册映射关系,而且使用 ReferenceTracking 解决了循环问题。
2. HigLabo.Mapper 也不需要提前注册映射关系(看来牛人都对提前注册很不爽),支持 object 转换为Dictionary,提出了 PostAction 概念(自动映射出目标对象后,还可以执行自定义动作进行手工赋值,这样就不需要费力实现 Flattening 什么的了)。但试用过程中,发现不能实现 Array 到 List 的转换,而且作者也不打算改。。。
既然各个 Mapper 都不太顺手,并且这段时间疫情封控,于是决定自己手撸一个POCO的 Mapper,目标如下:
1. 不需要提前注册映射关系
2. 像 json序列化/反序列化一样,同名属性尽可能映射(比如 int? 到 enum)
3. 增加 HigLabo.Mapper的PostAction概念
4. 使用 表达式树/Emit 提高速度
编写过程中参考了 TinyMapper 和UltraMapper的代码,使用示例:
1 public class Person 2 { 3 public int Id { get; set; } 4 5 public string Name { get; set; } 6 // Same Name, different type 7 public byte? Age { get; set; } 8 } 9 10 public class PersonDto 11 { 12 public int Id { get; set; } 13 14 public string Name { get; set; } 15 16 public int? Age { get; set; } 17 18 public string Description { get; set; } 19 } 20 21 class Program 22 { 23 static void Main(string[] args) 24 { 25 // register PostAction 26 ZK.Mapper.SimpleMapper.Default.SetPostAction<Person, PersonDto>((p, dto) => 27 { 28 if (dto == null) 29 { 30 return dto; 31 } 32 dto.Description = p.Age.HasValue ? $"{p.Name} age is {p.Age}" : $"{p.Name} age is unknown"; 33 return dto; 34 }); 35 36 var p = new Person() 37 { 38 Id = 1, 39 Name = "john", 40 Age = 32 41 }; 42 43 var dto = ZK.Mapper.SimpleMapper.Default.Map<PersonDto>(p); 44 Console.WriteLine(dto.Description); 45 } 46 }
写写这里面的总结吧
1. 内部Mapper都是泛型的,但使用时传入的source很可能是 object,所以都是使用 反射创建泛型化的Mapper实例,然后建立TypePair的对应关系,这样就解偶了泛型
2. Emit 和 表达式树原理都是一样的,建立IL代码,所以效率非常接近
3. 如果能像 AutoMapper 那样提前注册所有映射关系,速度优化的手段会更多,估计这也是 TinyMapper 转成提前注册的原因吧。很多 Mapper 的性能测试都号称比 AutoMapper 快,但引用的都是老版本的 AutoMapper,但现在 AutoMapper 非常快,在一些简单测试里赶上了 TinyMapper。如果添加了很多特色功能,却很拖累速度。当然我还是觉得只要不是数量级的差距,都不太重要。
4. 我的潜意识里 SimpleMapper 就为解决当前项目的问题,比如从数据库中读出来对象,映射成Dto后,就不会被再使用了,所以SimpleMapper默认是浅拷贝。所以我也不打算发布到nuget。
5. SimpleMapper 功能不多,也没有为性能做太多优化,所以结构比较清晰,有兴趣的朋友阅读起来应该不难。