Mapster 映射后只读属性变成 0,先确认同类型成员是否被克隆
带无参构造函数、只读值和可写属性的成员,在 Mapster 10.0.8 映射后可能成为新对象,构造时保存的只读值没有被带过去。按对象隔离要求显式选择引用赋值或构造映射,不依赖旧版本的偶然行为。
源对象的 ReadOnlyValue 是 42,映射到目标后却变成 0;可写属性仍保留,而 ReferenceEquals 从 true 变成 false。使用 Mapster 时,先检查丢值的成员是不是与源成员同类型,以及该类型是否同时存在无参构造函数、只读属性和可写属性。
这一组合在 10.0.7 与 10.0.8 间出现过行为差别:前者的对应路径沿用引用,后者创建新对象再复制可写成员。无参构造留下的只读默认值无法通过 setter 补回。它不等于数据源中的值已经变成零,也不能把旧版本的引用行为当成所有映射的长期保证。
先决定目标应不应该共享这个对象
如果目标的成员本来就应该引用同一个对象,可以只对这个类型对明确采用直接赋值。下面假定 Source 和 Destination 都有 Sample Member,且业务允许共享 Sample:
using Mapster;
var config = new TypeAdapterConfig();
config.NewConfig<Sample, Sample>()
.DirectAssignmentForSameType(true);
config.NewConfig<Source, Destination>();
config.Compile();
Destination result = source.Adapt<Destination>(config);
片段中的 source、Source、Destination 和 Sample 沿用应用的类型;配置应在编译映射及创建使用者之前完成。正式 10.0.13 的 ClassAdapter 提供这条同类型直接赋值分支,不能等配置已经编译后再随手修改共享实例。
这个选择会同时共享可写属性。之后通过 result.Member 修改对象,也会影响源对象看到的同一实例。若目标用于缓存、不同请求或需要独立修改的 DTO,不能只为恢复 42 就引入共享状态。
需要副本时,明确调用能保存只读值的构造方法
目标必须是新对象时,应让映射经过业务需要的构造函数,而不是只依赖无参构造再赋 setter。可以评估该类型的构造映射配置,或写一个局部转换函数,逐项带上只读构造参数和可写值。
不能把 MapToConstructor(true) 当作对所有模型自动正确的开关。构造参数名称、匹配规则、重载与自定义映射都会影响选择;正式 10.0.13 的实现会按候选构造函数生成映射。需要确定某个构造方法时,应对照实际模型明确指定,确认没有遗漏其他字段或绕过构造校验。
全局 ShallowCopyForSameType 会扩大引用共享范围。只解决一个 Sample 的迁移时,局部类型配置通常更便于评估;项目已有自定义转换器或更新映射时,还应检查实际调用是否走这条配置,不混用 MapToTarget 的原地修改语义。
验收既要看值,也要看对象身份
为只读值与可写值各选一个非默认输入,同时检查目标值、引用身份和后续修改的影响。采用直接赋值时,应明确接受共享;采用构造副本时,应确认新对象保留所有必要状态,且修改目标不会意外修改源对象。
再覆盖 null 成员与真实应用的嵌套映射,不只单独调用 Sample 到 Sample。升级 Mapster 后也应保留这些约定,避免下一次映射策略变化时,把“克隆还是共享”再次交给没有写明的默认行为。


