资讯动态

Mapster 构造函数映射指南:用 ConstructUsing 与 MapToConstructor 掌控目标对象创建

发布时间:2026/9/18 22:37:47 来源:尧图企业网站定制
Mapster 构造函数映射指南用 ConstructUsing 与 MapToConstructor 掌控目标对象创建【免费下载链接】MapsterA fast, fun and stimulating object to object Mapper项目地址: https://gitcode.com/GitHub_Trending/ma/MapsterMapster 默认通过无参构造函数创建目标对象但在实际项目中DTO 常常只暴露带参构造函数、依赖工厂方法或需要对象初始化器来预置默认值。本文基于 Mapster 的ConstructUsing与MapToConstructor两大配置项完整讲解如何接管目标对象的创建过程、如何按构造函数参数自动映射、以及如何显式指定特定构造函数并给出仓库源码级的实现佐证帮助你写出可复制、可运行的映射配置。目录一、默认创建方式的局限二、用 ConstructUsing 自定义目标对象创建无参构造函数场景非默认构造函数场景对象初始化器场景带 destination 参数的重载三、用 MapToConstructor 映射到构造函数全局默认开启类型对级别开启自定义构造函数参数映射Pascal case多构造函数时如何自动选择显式指定 ConstructorInfo四、源码级原理剖析构造工厂在 Settings 中的存储构造函数选择算法无默认构造函数时的错误提示五、典型实战场景映射到接口类型搭配 AfterMapping 使用 destination 重载六、相关文档与测试一、默认创建方式的局限Mapster 的核心Adapt操作在创建目标对象时默认期望目标类型具备无参构造函数empty constructor。在 BaseAdapter.cs 中当目标类型没有默认构造函数时会抛出如下异常No default constructor for type {arg.DestinationType.Name}, please use ConstructUsing or MapWith这说明 Mapster 本身已经内置了两种解决方案ConstructUsing自定义创建逻辑或MapWith整体替换映射逻辑。本文聚焦前者并延伸到相关的MapToConstructor配置。此外默认情况下 Mapster只会映射到字段和属性fields and properties构造函数参数并不在默认映射范围内。要让映射结果直接通过构造函数注入需要使用MapToConstructor显式开启。二、用 ConstructUsing 自定义目标对象创建ConstructUsing允许你提供一个函数调用来创建目标对象而不是使用默认的无参对象创建方式。该函数返回一个目标类型的实例你可以调用自己的构造函数、工厂方法或任何能产出期望类型对象的代码。无参构造函数场景当目标对象创建不需要源对象参与时使用无参重载TypeAdapterConfigTSource, TDestination.NewConfig() .ConstructUsing(() new TDestination());对应源码位于 TypeAdapterSetter.cspublic TypeAdapterSetterTDestination ConstructUsing(ExpressionFuncTDestination constructUsing) { this.CheckCompiled(); Settings.ConstructUsingFactory arg constructUsing; return this; }可以看到ConstructUsing接受的是表达式树ExpressionFunc...而非普通委托这意味着它会被内联进 Mapster 编译生成的映射表达式中而不是作为外部委托调用从而不影响映射性能。非默认构造函数场景最典型的用法是调用目标类型的非默认构造函数从源对象取参//Example using a non-default constructor TypeAdapterConfigTSource, TDestination.NewConfig() .ConstructUsing(src new TDestination(src.Id, src.Name));源码中对应的是带源对象参数的重载位于 TypeAdapterSetter.cspublic TypeAdapterSetterTSource, TDestination ConstructUsing(ExpressionFuncTSource, TDestination constructUsing) { Settings.ConstructUsingFactory arg constructUsing; return this; }仓库测试 WhenUsingNonDefaultConstructor.cs 验证了这一行为即使目标类型同时具备无参构造函数配置了ConstructUsing后映射仍会走自定义构造函数且Unmapped属性被赋值为传入的固定值unmapped同时Id、Name等可映射成员依旧正常映射。对象初始化器场景你同样可以使用对象初始化器object initializer预置某些目标对象自己的成员值//Example using an object initializer TypeAdapterConfigTSource, TDestination.NewConfig() .ConstructUsing(src new TDestination{Unmapped unmapped});这种写法在 WhenUsingNonDefaultConstructor.cs 的Dest_Calls_Calls_Factory_Method_With_ConstructUsing测试中也有对应验证。需要注意ConstructUsing的表达式体在投影Projection场景下只支持New和MemberInit两种节点类型。在 ClassAdapter.cs 中如果投影模式下传入的是其他表达式如方法调用、复杂逻辑会抛出InvalidOperationExceptionConstructUsing for projection is support only New and MemberInit expression.带 destination 参数的重载当目标对象已存在例如映射到已有目标对象时ConstructUsing还有一个带destination参数的重载可以读取目标对象的当前状态参与构造//Example using an overload with destination parameter TypeAdapterConfigTSource, TDestination.NewConfig() .ConstructUsing((src, destination) new TDestination(src.Id, destination?.Name ?? src.Name));对应源码位于 TypeAdapterSetter.cs该重载只在UseDestinationValue或目标值存在时才有意义。仓库测试 WhenPerformingAfterMapping.cs 也展示了该重载与 AfterMapping 组合使用的场景。三、用 MapToConstructor 映射到构造函数默认情况下 Mapster 只映射字段和属性。通过MapToConstructor可以将映射目标改为构造函数参数让不可变 DTO只有只读属性、没有 setter也能被完整填充。全局默认开启在全局设置中开启所有类型对默认映射到构造函数//global level TypeAdapterConfig.GlobalSettings.Default.MapToConstructor(true);类型对级别开启也可以只针对某个类型对开启//type pair TypeAdapterConfigPoco, Dto.NewConfig().MapToConstructor(true);MapToConstructor(bool)的源码实现位于 TypeAdapterSetter.cs开启时在 Settings 中写入*标记关闭时写入nullpublic static TSetter MapToConstructorTSetter(this TSetter setter, bool value) where TSetter : TypeAdapterSetter { setter.CheckCompiled(); setter.Settings.MapToConstructor value ? * : null; return setter; }CheckCompiled()意味着一旦该配置对应的映射已经编译完成就不能再修改否则会抛异常——这也是 Mapster 所有设置项的统一行为。自定义构造函数参数映射Pascal case当构造函数参数名与源成员名不一致时需要自定义映射此时必须使用Pascal case帕斯卡命名即首字母大写class Poco { public string Id { get; set; } ... } class Dto { public Dto(string code, ...) { ... } }TypeAdapterConfigPoco, Dto.NewConfig() .MapToConstructor(true) .Map(Code, Id); //use Pascal caseMap(Code, Id)的含义是目标构造函数的Code参数Pascal case 形式从源对象的Id属性取值。构造函数参数在内部被规范化为 Pascal case 表示因此自定义映射必须遵守这一约定。多构造函数时如何自动选择如果目标类有 2 个或更多构造函数Mapster 会自动选择能够满足映射的、参数数量最多的那个构造函数class Poco { public int Foo { get; set; } public int Bar { get; set; } } class Dto { public Dto(int foo) { ... } public Dto(int foo, int bar) { ...} //-- Mapster will use this constructor public Dto(int foo, int bar, int baz) { ... } }在这个例子中Poco只有Foo、Bar两个可映射成员因此Dto(int foo, int bar)是能完全映射的参数最多的构造函数会被选中Dto(int foo, int bar, int baz)虽然参数更多但因为Poco没有Baz成员无法满足映射。显式指定 ConstructorInfo除了自动选择你还可以通过反射拿到ConstructorInfo并显式传入var ctor typeof(Dto).GetConstructor(new[] { typeof(int), typeof(int) }); TypeAdapterConfigPoco, Dto.NewConfig() .MapToConstructor(ctor);对应源码位于 TypeAdapterSetter.cs该方法包含两个重要的运行时校验构造函数声明类型必须能赋值给TDestination否则抛出ArgumentException(Constructor cannot be assigned to type TDestination)构造函数的声明类型不能是抽象类否则抛出ArgumentException(Constructor of abstract type cannot be created)。仓库测试 WhenMappingToConstructor.cs 验证了显式指定构造函数的效果Dto定义了三个构造函数显式指定Dto(string id)后映射结果中Id被正确赋值而Name为 null、Age为 0其余可写属性Prop仍通过普通属性映射填充。四、源码级原理剖析构造工厂在 Settings 中的存储无论是ConstructUsing的哪个重载最终都统一写入Settings.ConstructUsingFactory其类型为FuncCompileArgument, LambdaExpression定义在 TypeAdapterSettings.cs。编译时CompileArgument.cs 通过GetConstructUsing()惰性求值该工厂internal LambdaExpression? GetConstructUsing() { if (_fetchConstructUsing) return _constructUsing; _constructUsing Settings.ConstructUsingFactory?.Invoke(this); _fetchConstructUsing true; return _constructUsing; }_fetchConstructUsing标志保证了同一个编译参数实例中工厂只被调用一次避免重复求值影响性能。构造函数选择算法MapToConstructor(true)与自动选择构造函数的完整逻辑在 ClassAdapter.cs 的CreateInstantiationExpression中若目标类型有默认构造函数、或已配置ConstructUsing且未配置MapToConstructor则走基类的默认实例化路径否则从Settings.MapToConstructor中读取ConstructorInfo若没有显式指定则通过destType.GetConstructors()获取所有构造函数按参数数量降序排序OrderByDescending(it it.GetParameters().Length)逐个尝试CreateClassConverter生成映射返回第一个能成功完成参数映射的构造函数FirstOrDefault(it it ! null)如果全部尝试失败则回退到第一个构造函数的非严格模式GetConstructorModel(constructors[0], false)若仍无法生成则回退到默认的实例化表达式。值得注意的是代码中destType还处理了目标为接口的情况通过DynamicTypeGenerator.GetTypeForInterface为接口动态生成实现类型再在其上寻找构造函数见 ClassAdapter.cs。无默认构造函数时的错误提示在 BaseAdapter.cs 中当最终无法创建目标实例且目标类型没有默认构造函数时会抛出带有明确指引的异常信息提示开发者改用ConstructUsing或MapWith。这条错误信息实际上就是何时应该使用本文配置项的最直接判断标准。五、典型实战场景映射到接口类型当目标是接口时ConstructUsing可以配合动态生成的接口实现类型使用。仓库测试 WhenUsingNonDefaultConstructor.cs 展示了将SimplePoco映射到ISimpleDtoWithDefaultConstructor接口时通过ConstructUsing提供具体实现类实例的做法TypeAdapterConfigSimplePoco, ISimpleDtoWithDefaultConstructor.NewConfig() .IgnoreNullValues(true) .ConstructUsing(src new SimpleDtoWithDefaultConstructor { Unmapped unmapped }) .Compile(); var dto TypeAdapter.AdaptISimpleDtoWithDefaultConstructor(simplePoco);搭配 AfterMapping 使用 destination 重载带destination参数的ConstructUsing重载适合在目标对象已存在的合并场景中使用它允许构造逻辑参考目标对象的当前值如destination?.Name ?? src.Name这样的回退策略。这种先构造、再 AfterMapping 收尾的组合在 WhenPerformingAfterMapping.cs 中均有测试覆盖。六、相关文档与测试原文档Constructor mapping设置项总览Settings 目录相关主题还包括 Setting values、Constructor 相关核心实现TypeAdapterSetter.csConstructUsing、MapToConstructor各重载定义ClassAdapter.cs构造函数自动选择算法BaseAdapter.cs无默认构造函数的异常提示TypeAdapterSettings.csConstructUsingFactory与MapToConstructor设置存储测试用例WhenMappingToConstructor.cs自动选择与显式ConstructorInfo两种模式WhenUsingNonDefaultConstructor.cs非默认构造函数、工厂方法、接口目标三种场景WhenPerformingAfterMapping.csConstructUsing与AfterMapping组合核心要点回顾ConstructUsing用于完全接管目标对象的创建自定义构造函数、工厂方法或初始化器MapToConstructor用于让映射结果直接注入构造函数参数当存在多个构造函数时 Mapster 自动选择参数最多且能完全映射的那个也可以显式传入ConstructorInfo精确控制自定义构造参数映射必须使用 Pascal case。两者都能与全局Default设置、投影Projection、接口目标等场景无缝配合。【免费下载链接】MapsterA fast, fun and stimulating object to object Mapper项目地址: https://gitcode.com/GitHub_Trending/ma/Mapster创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价