重複造輪子 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 功能不多,也沒有為性能做太多優化,所以結構比較清晰,有興趣的朋友閱讀起來應該不難。