AutoMapper的源碼分析

最近有一個小項目須要提供接口給第三方使用,接口會獲得一個大的XML的字符串大約有8個對象100多個字段,在映射到Entity只能經過反射來賦值避免重複的賦值,可是明顯感受到性能降低嚴重,由於之前接觸過AutoMapper因此寫了一篇博客記錄其中的實現原理。git

github 上能夠下載AutoMapper 源碼,直接打開sln 文件 就能夠看到項目結構。github

項目結構很是清晰,一個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的實現原理,下面就是一個大體的分析過程。app

代碼建立了一個MapperConfiguration對象,裏面傳遞了一個Action到構造方法,而這個Action裏建立了Map 的信息,這是最簡單的demo因此使用默認的字段Map方式。ide

 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中。ui

 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運行速度快的緣由。這個項目挺小的而且代碼工整可讀性挺強的,但願你們在閱讀源代碼可以學的更多。最後謝謝你們。this

相關文章
相關標籤/搜索