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 展开,系统讲解EnableNonPublicMembers、AdaptMember特性、Map与IncludeMember四种映射非公开成员的途径,并结合仓库源码与测试用例说明其底层判定逻辑与适用边界,帮助你按需、安全地打开非公开成员映射能力。
默认行为:非公开成员默认不参与映射
在开始配置之前,先明确 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>();它包含两条关键规则:
- 只影响非公开成员:
AccessModifier不带NonPublic标志的成员返回null,即不干预、交给后续逻辑决定; - 排除编译器生成字段:对于带有
CompilerGeneratedAttribute的字段(典型如自动属性背后的 backing field),即使是非公开也不会被映射,避免破坏属性映射的正常流程。
访问修饰符的判定来源
成员的AccessModifier由 ReflectionUtils.cs 中的GetAccessModifier重载根据反射信息换算得出:IsPublic对应Public,IsFamily对应Protected,IsAssembly对应Internal,IsFamilyOrAssembly对应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(携带DestinationMemberName与SourceMemberName),在编译阶段作为解析器参与映射表达式的构建,从而绕过默认的成员筛选直接建立“名称到名称”的赋值通道。
方式四:IncludeMember精确控制访问修饰符
EnableNonPublicMembers是一刀切开关,而IncludeMember允许你用谓词自定义哪些成员可以映射,典型用法是按访问修饰符过滤:
TypeAdapterConfig.GlobalSettings.Default .IncludeMember((member, side) => member.AccessModifier == AccessModifier.Internal || member.AccessModifier == AccessModifier.ProtectedInternal);上面的配置意味着:只有internal与protected internal成员参与映射,而private、protected等其余非公开成员仍保持默认不映射。
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 会将其视为类似string、int的“基本值”,走直接赋值路径而非对象映射路径(可参考 ReflectionUtils.cs 中IsPrimitiveKind对可转换基础类型的判定思路)。因此:
- 先通过
EnableNonPublicMembers(true)(类型对或全局均可)开启非公开成员支持; - 再显式调用
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_Correctly | EnableNonPublicMembers(true)后私有字段正确映射 |
Should_Map_Private_Property_To_New_Object_Correctly | EnableNonPublicMembers(true)后私有属性正确映射 |
Should_Map_To_Private_Fields_Correctly | 反向映射:公开 DTO → 私有字段目标 |
Should_Map_To_Private_Properties_Using_Include | IncludeMember谓词放行 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谓词:在总开关之外做访问修饰符级别的精确筛选,例如只允许internal与protected internal成员参与映射。
理解ShouldMapMember与AllowNonPublic的判定顺序(ShouldMapMember.cs、ReflectionUtils.cs),能帮助你预测配置叠加后的实际行为,避免在遗留系统或封闭类型迁移时踩到“映射静默丢失”的坑。
【免费下载链接】MapsterA fast, fun and stimulating object to object Mapper项目地址: https://gitcode.com/GitHub_Trending/ma/Mapster
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考