Mapster 映射非公开成员完全指南:从 EnableNonPublicMembers 到细粒度访问修饰符控制
2026/9/18 2:24:02 网站建设 项目流程

Mapster 映射非公开成员完全指南:从 EnableNonPublicMembers 到细粒度访问修饰符控制

【免费下载链接】MapsterA fast, fun and stimulating object to object Mapper项目地址: https://gitcode.com/GitHub_Trending/ma/Mapster

Mapster 默认只映射类型的公开(public)成员,但在处理遗留实体、ORM 代理类或带有private set属性、私有字段的 DTO 时,往往需要将非公开成员也纳入映射范围。本文围绕 Mapster 官方文档 Mapping non-public members 展开,系统讲解EnableNonPublicMembersAdaptMember特性、MapIncludeMember四种映射非公开成员的途径,并结合仓库源码与测试用例说明其底层判定逻辑与适用边界,帮助你按需、安全地打开非公开成员映射能力。

默认行为:非公开成员默认不参与映射

在开始配置之前,先明确 Mapster 的默认策略:非公开成员(private / protected / internal / protected internal)默认不会被映射。这一行为有测试用例直接背书——在 WhenMappingPrivateFieldsAndProperties.cs 中:

  • Default_Settings_Should_Not_Map_Private_Fields_To_New_Object:默认配置下,私有字段_id不会被写入目标 DTO;
  • Default_Settings_Should_Not_Map_Private_Properties_To_New_Object:默认配置下,私有属性Name同样不会被映射。

因此,要让非公开成员参与映射,必须显式地进行配置。Mapster 共提供四种途径,下文逐一展开。

方式一:EnableNonPublicMembers扩展方法

EnableNonPublicMembers(bool)是最直接的开关:置为true后,Mapster 将允许对所有非公开成员进行读写映射。

// 类型对级别:仅作用于 Poco -> Dto 这一对类型 TypeAdapterConfig<Poco, Dto>.NewConfig().EnableNonPublicMembers(true); // 全局级别:作用于所有类型对 TypeAdapterConfig.GlobalSettings.Default.EnableNonPublicMembers(true);

从源码看,该方法的核心实现只是把布尔值写入设置项 TypeAdapterSetter.cs:

public static TSetter EnableNonPublicMembers<TSetter>(this TSetter setter, bool value) where TSetter : TypeAdapterSetter { setter.CheckCompiled(); setter.Settings.EnableNonPublicMembers = value; return setter; }

该设置真正发挥作用的位置在成员筛选逻辑 ReflectionUtils.cs 的ShouldMapMember中:当EnableNonPublicMembers == true时,Mapster 会调用Mapster.ShouldMapMember.AllowNonPublic谓词对成员放行。

底层判定:AllowNonPublic谓词

AllowNonPublic定义于 ShouldMapMember.cs:

public static readonly Func<IMemberModel, MemberSide, bool?> AllowNonPublic = (model, _) => (model.AccessModifier & AccessModifier.NonPublic) == 0 ? (bool?) null : !(model.Info is FieldInfo) || !model.HasCustomAttribute<CompilerGeneratedAttribute>();

它包含两条关键规则:

  1. 只影响非公开成员AccessModifier不带NonPublic标志的成员返回null,即不干预、交给后续逻辑决定;
  2. 排除编译器生成字段:对于带有CompilerGeneratedAttribute的字段(典型如自动属性背后的 backing field),即使是非公开也不会被映射,避免破坏属性映射的正常流程。

访问修饰符的判定来源

成员的AccessModifier由 ReflectionUtils.cs 中的GetAccessModifier重载根据反射信息换算得出:IsPublic对应PublicIsFamily对应ProtectedIsAssembly对应InternalIsFamilyOrAssembly对应ProtectedInternal,其余归为Private。这为下文IncludeMember按修饰符过滤提供了数据基础。

TwoWays 支持

EnableNonPublicMembers同样支持双向配置。在 TypeAdapterSetter.cs 中,TwoWaysTypeAdapterSetter<TSource, TDestination>会将该设置同时应用到 Source→Destination 与 Destination→Source 两个方向。

方式二:AdaptMember特性

当不想全局放开非公开成员、只希望针对个别成员映射时,可以使用AdaptMember特性按成员精确指定

public class Product { [AdaptMember] private string HiddenId { get; set; } public string Name { get; set; } }

AdaptMember定义于 AdaptMemberAttribute.cs,特性本身允许作用于字段、属性、构造参数三种目标(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property),并提供两个可选属性:

  • Name:指定成员在映射时使用的名称,可用于改名映射(如[AdaptMember("Id")] private string HiddenId);
  • Side:限定该特性只对 Source 侧或 Destination 侧生效(MemberSide枚举),不指定则两侧都生效。

在底层,ShouldMapMember.cs 中的AllowAdaptMember谓词会检查成员是否携带AdaptMemberAttribute,命中即返回true放行,同样遵循Side匹配规则。这意味着即使没有开启EnableNonPublicMembers,被[AdaptMember]标注的非公开成员也能单独参与映射。

方式三:Map扩展方法按名称映射私有成员

如果目标成员与源成员名称不同,可以借助Map扩展方法,通过指定成员名完成对私有成员的映射:

TypeAdapterConfig<TSource, TDestination> .NewConfig() .Map("PrivateDestName", "PrivateSrcName");

第一个参数是目标成员名,第二个参数是源成员名。底层实现位于 TypeAdapterSetter.cs:该方法向Settings.Resolvers追加一个InvokerModel(携带DestinationMemberNameSourceMemberName),在编译阶段作为解析器参与映射表达式的构建,从而绕过默认的成员筛选直接建立“名称到名称”的赋值通道。

方式四:IncludeMember精确控制访问修饰符

EnableNonPublicMembers是一刀切开关,而IncludeMember允许你用谓词自定义哪些成员可以映射,典型用法是按访问修饰符过滤:

TypeAdapterConfig.GlobalSettings.Default .IncludeMember((member, side) => member.AccessModifier == AccessModifier.Internal || member.AccessModifier == AccessModifier.ProtectedInternal);

上面的配置意味着:只有internalprotected internal成员参与映射,而privateprotected等其余非公开成员仍保持默认不映射。

IncludeMember的签名是Func<IMemberModel, MemberSide, bool>member参数暴露成员模型(含上文提到的AccessModifier属性),side参数标明当前成员处于 Source 还是 Destination 侧,因此你完全可以写出更复杂的条件,例如“仅当在目标侧时才允许 internal 属性”。实现上,TypeAdapterSetter.cs 将谓词包装进Settings.ShouldMapMember列表:谓词返回true时成员被放行,返回false则被忽略,返回null(即谓词不命中)则交给后续规则继续判断。它与EnableNonPublicMembers叠加生效的关系,适合在全局开关的基础上做二次收窄。

对应的测试用例 WhenMappingPrivateFieldsAndProperties.cs 演示了IncludeMember((model, side) => model.AccessModifier == AccessModifier.Protected)的用法,验证了 protected 属性在开启该谓词后可以正确映射。

重要注意事项:无公共属性的类型会被当作原始类型

官方文档特别强调了一个容易踩坑的场景(见 Mapping non-public members 的 "Note" 一节):

如果类型不包含任何公共属性,Mapster 会将该类型当作原始类型(primitive)处理,此时必须显式声明类型对,才能确保非公开成员映射真正生效。

TypeAdapterConfig.GlobalSettings.Default.EnableNonPublicMembers(true); TypeAdapterConfig<PrivatePoco, PrivateDto>.NewConfig();

原因在于 Mapster 判断类型是否可映射时依赖其可访问成员。若一个类型没有任何公共可映射成员,Mapster 会将其视为类似stringint的“基本值”,走直接赋值路径而非对象映射路径(可参考 ReflectionUtils.cs 中IsPrimitiveKind对可转换基础类型的判定思路)。因此:

  1. 先通过EnableNonPublicMembers(true)(类型对或全局均可)开启非公开成员支持;
  2. 再显式调用TypeAdapterConfig<PrivatePoco, PrivateDto>.NewConfig()注册类型对,让 Mapster 以对象映射的方式处理这一对类型。

两者缺一不可,否则配置不会如预期生效。

测试验证:端到端行为一览

仓库中的 WhenMappingPrivateFieldsAndProperties.cs 覆盖了非公开成员映射的主要行为,可作为配置正确性的参照:

测试方法验证点
Default_Settings_Should_Not_Map_Private_Fields_To_New_Object默认不映射私有字段
Default_Settings_Should_Not_Map_Private_Properties_To_New_Object默认不映射私有属性
Should_Map_Private_Field_To_New_Object_CorrectlyEnableNonPublicMembers(true)后私有字段正确映射
Should_Map_Private_Property_To_New_Object_CorrectlyEnableNonPublicMembers(true)后私有属性正确映射
Should_Map_To_Private_Fields_Correctly反向映射:公开 DTO → 私有字段目标
Should_Map_To_Private_Properties_Using_IncludeIncludeMember谓词放行 protected 属性
Test_Dictionary字典源数据经EnableNonPublicMembers映射到目标的私有属性

其中Test_Dictionary场景值得注意:它展示了ForType<IDictionary<string, object>, Pet>().EnableNonPublicMembers(true)的用法,即字典键"Color"可以写入Pet的私有属性Color,验证了非公开成员映射在字典驱动场景下同样生效。

总结:四种方式的选用建议

  • EnableNonPublicMembers(true):全局或类型对级别的总开关,适合整体放开非公开成员映射,搭配“显式注册类型对”应对全私有类型;
  • [AdaptMember]特性:细粒度定点映射,适合仅个别私有成员需要映射、且希望保持默认策略不变的场景,还支持Name改名与Side方向限定;
  • Map("dest", "src"):名称不一致时的显式桥接,通过解析器直接绑定源/目标成员名;
  • IncludeMember谓词:在总开关之外做访问修饰符级别的精确筛选,例如只允许internalprotected internal成员参与映射。

理解ShouldMapMemberAllowNonPublic的判定顺序(ShouldMapMember.cs、ReflectionUtils.cs),能帮助你预测配置叠加后的实际行为,避免在遗留系统或封闭类型迁移时踩到“映射静默丢失”的坑。

【免费下载链接】MapsterA fast, fun and stimulating object to object Mapper项目地址: https://gitcode.com/GitHub_Trending/ma/Mapster

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询