MemberwiseClone() 仅支持浅拷贝,对引用类型只复制地址;深拷贝推荐 System.Text.Json 序列化反序列化,注意循环引用、DateTimeKind 丢失等问题,或显式实现 ICloneable 并递归克隆。

用 MemberwiseClone() 只能得到浅拷贝,别信文档里“复制所有字段”的说法
它确实复制了所有字段值,但对引用类型字段,只复制了引用地址——新旧对象仍指向同一堆内存。常见现象是改副本的 List<string>,原对象也跟着变;或者修改副本里的 Person.Address.Street,原对象地址也变了。
实操建议:
- 除非对象全是值类型(
int、DateTime、struct且不含引用字段),否则MemberwiseClone()不适合当“克隆”用 - 如果类标记了
[Serializable],可临时用二进制序列化绕过,但注意BinaryFormatter已被弃用且不支持 .NET 6+ 默认启用 - 更稳妥的做法:显式实现
ICloneable并在Clone()方法里手动 new 引用字段
JSON 序列化反序列化是最省心的深拷贝方案(.NET Core 3.0+)
用 System.Text.Json 做一次序列化 + 反序列化,天然跳过引用共享问题,且无需标记 [Serializable] 或写额外属性。
实操建议:
- 基础用法:
JsonSerializer.Deserialize<T>(JsonSerializer.Serialize(obj)) - 注意循环引用会直接抛
InvalidOperationException: "A possible object cycle was detected",需配JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve - 不支持
DateTimeKind.Unspecified在反序列化后丢失 kind 的问题,可加options.Converters.Add(new JsonTimeConverter())补救 - 性能比手动 new 慢 3–5 倍,但开发效率高;高频调用场景建议缓存
JsonSerializerOptions实例
AutoMapper 配置 ForMember 时漏掉嵌套对象,深拷贝就失效
很多人以为配好 CreateMap<A, A>() 就能自动深拷贝,其实默认行为仍是浅拷贝——AutoMapper 对引用类型字段默认复用源对象实例。
实操建议:
- 必须显式配置每个嵌套引用字段:
ForMember(dest => dest.Items, opt => opt.MapFrom(src => src.Items.ToList())) - 或全局启用深度克隆:在
CreateMap后链式调用ConvertUsing((src, dest) => JsonSerializer.Deserialize<T>(JsonSerializer.Serialize(src))) - 若类型含
readonly字段或无参构造函数被禁用,AutoMapper 会静默失败,建议加单元测试验证副本是否真的独立
自定义 Clone() 方法里忘了递归克隆子对象,改着改着就串了
手写克隆逻辑最可控,但也最容易在第三层引用上漏掉 new。比如 Order → Customer → Address,只 new 了 Customer 却沿用 Address 原实例。
实操建议:
- 每个引用字段都检查是否实现了
ICloneable,有则调Clone(),没有则手动 new + 赋值 - 集合类别直接赋值
Items = new List<Item>(source.Items)——这只是浅拷贝,要改成Items = source.Items.Select(i => i.Clone()).ToList() - 避免在
Clone()里调用虚方法或触发属性 setter,可能引发未预期副作用(如触发 EF 的变更追踪)
深拷贝真正的复杂点不在第一层,而在你根本没意识到那个 private readonly Dictionary<string, Config> 也被共享了。多一层引用,就多一个得亲手断开的地方。