最近有一個小專案需要提供介面給第三方使用,介面會得到一個大的XML的字串大約有8個物件100多個欄位,在對映到Entity只能通過反射來賦值避免重複的賦值,但是明顯感覺到效能下降嚴重,因為以前接觸過AutoMapper所以寫了一篇部落格記錄其中的實現原理。
在github 上可以下載AutoMapper 原始碼,直接開啟sln 檔案 就可以看到專案結構。
專案結構非常清晰,一個AutoMapper的實現類,兩個測試庫一個單元測試一個整合測試還有一個效能測試。下面就是AutoMapper的測試程式碼。
var config = new MapperConfiguration(cfg => cfg.CreateMap<TestSource, TestDestination>() ); var mapper1 = config.CreateMapper(); var fooDto = mapper1.Map<TestDestination>(new TestSource{ MyProperty = 1 });
專案不大,除錯的時候你就可以明白AutoMapper的實現原理,下面就是一個大致的分析過程。
程式碼建立了一個MapperConfiguration物件,裡面傳遞了一個Action到構造方法,而這個Action裡建立了Map 的資訊,這是最簡單的demo所以使用預設的欄位Map方式。
1 public MapperConfiguration(Action<IMapperConfigurationExpression> configure) 2 : this(Build(configure)) 3 { 4 } 5 6 private static MapperConfigurationExpression Build(Action<IMapperConfigurationExpression> configure) 7 { 8 var expr = new MapperConfigurationExpression(); 9 configure(expr); 10 return expr; 11 }
上面就是構造方法處理的邏輯,實際上就是build方法創造一個MapperConfigurationExpression物件,然後把這個物件傳給Action生成Mapper,這個Mapper的意思就是source Type和destination Type的欄位對映物件。我們繼續檢視這個第8行的程式碼在new這個物件的時候做了什麼初始化操作。這個MapperConfigurationExpression繼承了抽象類Profile,而在這個父類的構造方法裡初始化了IMemberConfiguration類,這個是為了制定map規則物件,預設是DefaultMember這個物件。下面是這個建立完MapperConfigurationExpression後呼叫構造方法。
1 public MapperConfiguration(MapperConfigurationExpression configurationExpression) 2 { 3 _mappers = configurationExpression.Mappers.ToArray(); 4 _resolvedMaps = new LockingConcurrentDictionary<TypePair, TypeMap>(GetTypeMap); 5 _executionPlans = new LockingConcurrentDictionary<MapRequest, Delegate>(CompileExecutionPlan); 6 _validator = new ConfigurationValidator(this, configurationExpression); 7 ExpressionBuilder = new ExpressionBuilder(this); 8 9 ServiceCtor = configurationExpression.ServiceCtor; 10 EnableNullPropagationForQueryMapping = configurationExpression.EnableNullPropagationForQueryMapping ?? false; 11 MaxExecutionPlanDepth = configurationExpression.Advanced.MaxExecutionPlanDepth + 1; 12 ResultConverters = configurationExpression.Advanced.QueryableResultConverters.ToArray(); 13 Binders = configurationExpression.Advanced.QueryableBinders.ToArray(); 14 RecursiveQueriesMaxDepth = configurationExpression.Advanced.RecursiveQueriesMaxDepth; 15 16 Configuration = new ProfileMap(configurationExpression); 17 Profiles = new[] { Configuration }.Concat(configurationExpression.Profiles.Select(p => new ProfileMap(p, configurationExpression))).ToArray(); 18 19 configurationExpression.Features.Configure(this); 20 21 foreach (var beforeSealAction in configurationExpression.Advanced.BeforeSealActions) 22 beforeSealAction?.Invoke(this); 23 Seal(); 24 }
在這個構造方法裡會將之前建立的MapperConfigurationExpression轉化成當前物件的各個欄位,其中LockingConcurrentDictionary是自己實現的執行緒安全的字典物件,構造方法傳遞取值的規則,重要的是Profiles欄位,這個儲存之前的action 傳遞的MapperConfigurationExpression物件,這個物件也就是source type和destination type 物件的相關資訊。最後重要的是seal 方法,根據相關資訊生產lambda程式碼。
public void Seal(IConfigurationProvider configurationProvider) { if(_sealed) { return; } _sealed = true; _inheritedTypeMaps.ForAll(tm => _includedMembersTypeMaps.UnionWith(tm._includedMembersTypeMaps)); foreach (var includedMemberTypeMap in _includedMembersTypeMaps) { includedMemberTypeMap.TypeMap.Seal(configurationProvider); ApplyIncludedMemberTypeMap(includedMemberTypeMap); } _inheritedTypeMaps.ForAll(tm => ApplyInheritedTypeMap(tm)); _orderedPropertyMaps = PropertyMaps.OrderBy(map => map.MappingOrder).ToArray(); _propertyMaps.Clear(); MapExpression = CreateMapperLambda(configurationProvider, null); Features.Seal(configurationProvider); }
上面就是核心程式碼,根據之前註冊的相關資訊生成一個lambda程式碼。CreateDestinationFunc建立一個lambda表示式,內容是new 一個destination物件,在CreateAssignmentFunc繼續擴充套件欄位賦值的lambda內容,其中欄位map規則就是之前新建MapperConfiguration
物件的時候建立的,如果沒有注入就是預設的匹配規則,CreateMapperFunc會產生一些規則,比如預設值賦值等等。這些生成的lambda物件會存在Typemap 物件的MapExpression中。
1 public LambdaExpression CreateMapperLambda(HashSet<TypeMap> typeMapsPath) 2 { 3 var customExpression = TypeConverterMapper() ?? _typeMap.CustomMapFunction ?? _typeMap.CustomMapExpression; 4 if(customExpression != null) 5 { 6 return Lambda(customExpression.ReplaceParameters(Source, _initialDestination, Context), Source, _initialDestination, Context); 7 } 8 9 CheckForCycles(typeMapsPath); 10 11 if(typeMapsPath != null) 12 { 13 return null; 14 } 15 16 var destinationFunc = CreateDestinationFunc(); 17 18 var assignmentFunc = CreateAssignmentFunc(destinationFunc); 19 20 var mapperFunc = CreateMapperFunc(assignmentFunc); 21 22 var checkContext = CheckContext(_typeMap, Context); 23 var lambaBody = checkContext != null ? new[] {checkContext, mapperFunc} : new[] {mapperFunc}; 24 25 return Lambda(Block(new[] {_destination}, lambaBody), Source, _initialDestination, Context); 26 }
後來的呼叫map的時候就會這些lambda方法,在之前說過,生成的lambda表示式會在記憶體中動態生成IL程式碼,這些花費的時間只有一次就是seal呼叫的時候,後面的時候去執行程式碼速度會快得多,因為這些動態生成的il程式碼在執行的時候速度和手寫程式碼執行的一樣的,所以程式碼執行的時候是非常快的,遠遠高於反射的速度,所以這就是AutoMapper執行速度快的原因。這個專案挺小的並且程式碼工整可讀性挺強的,希望大家在閱讀原始碼能夠學的更多。最後謝謝大家。