zoukankan      html  css  js  c++  java
  • AutoMapper的源码分析

    最近想写一个map 工具,在其中使用放射去给对象赋值感觉性能下降的很厉害,因为以前接触过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运行速度快的原因。这个项目挺小的并且代码工整可读性挺强的,希望大家在阅读源代码能够学的更多。最后谢谢大家。

  • 相关阅读:
    # 漏洞挖掘技巧清单 #
    CVE-2020-0796——SMBV3客户端/服务端远程代码执行漏洞(远程代码执行+权限提升)
    SSH公钥详细介绍及拓展
    滑动窗口(数据结构)
    反素数解析
    树状数组的基本操作
    Rochambeau POJ
    一般图最大匹配
    2020牛客暑期多校训练营(第一场)H Minimum-cost Flow
    A Bug's Life POJ
  • 原文地址:https://www.cnblogs.com/neilhu/p/14179070.html
Copyright © 2011-2022 走看看