重複造輪子 SimpleMapper

  接手的項目還在用 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 功能不多,也沒有為性能做太多優化,所以結構比較清晰,有興趣的朋友閱讀起來應該不難。

 

  SimpleMapper 程式碼地址

Tags: