深拷贝无开箱即用安全方案,应避免BinaryFormatter;优先用System.Text.Json或Newtonsoft.Json序列化,但需注意类型限制;高一致性场景宜手动实现,仅深拷可能被修改的引用字段。

浅拷贝用 MemberwiseClone 就够了,但深拷贝没有“开箱即用”的安全方案;别盲目套用序列化,尤其当对象含不可序列化成员、事件、委托或循环引用时,会直接抛异常。
什么时候必须用 MemberwiseClone 做浅拷贝
当你只需要隔离值类型字段,且明确接受引用类型字段仍共享同一实例时,MemberwiseClone 是最轻量、最可控的选择。它不依赖任何特性标记,也不触发反射或序列化开销。
- 必须是类(
class),不能是struct(结构体赋值本身就是值拷贝) - 方法需声明为
protected或public,且只能在当前类或派生类中调用 - 它不会调用任何构造函数,也不会执行字段初始化器逻辑
- 如果类里有
event字段,MemberwiseClone会复制事件的订阅列表引用——这常导致“副本一触发事件,原对象也响应”这类隐蔽 bug
用 BinaryFormatter 序列化做深拷贝已不推荐
BinaryFormatter 在 .NET 5+ 中已被标记为过时,.NET 6+ 默认禁用,且存在严重反序列化安全风险。即使你加了 [Serializable],遇到 Stream、Thread、DbContext 等类型仍会直接抛 SerializationException。
- 替代方案优先考虑
System.Text.Json:要求所有类型可 public 构造、所有字段/属性可读写,且不含循环引用 -
Newtonsoft.Json的JsonConvert.DeserializeObject<t>(JsonConvert.SerializeObject(obj))</t>更宽容,但会丢失私有 setter、只读集合、DateTimeKind精度等细节 - 若对象含
Expression、Delegate、IDisposable实现,序列化路径基本走不通
手动实现深拷贝的关键控制点
真正需要强一致性、高可控性的场景(如领域模型克隆、快照保存),应手写深拷贝逻辑。重点不是“全字段遍历”,而是“只深拷那些可能被修改的引用字段”。
- 只对明确需要隔离的引用类型字段调用其
Clone()或新建实例并逐字段赋值,比如List<t></t>→new List<t>(original)</t>,Dictionary<k></k>→new Dictionary<k>(original)</k> - 跳过只读字段(
readonly)、static字段、event字段——它们本就不该参与拷贝 - 对嵌套对象,递归调用其自有深拷贝方法(如自定义的
DeepClone()),而非统一走反射 - 注意字符串虽是引用类型,但因不可变性,在浅拷贝中行为等同于值类型,无需额外处理
为什么 ICloneable 接口几乎没用
ICloneable.Clone() 只是一个返回 object 的空方法签名,不承诺是深还是浅。.NET 官方文档早已注明:“此接口不提供类型安全性,且无法表达克隆语义”。实际项目中,调用方根本无法判断 someObj.Clone() 返回的是独立副本还是共享引用。
- 除非整个团队约定所有
Clone()都是深拷贝,并配合代码审查强制约束,否则极易引发运行时数据污染 - 更清晰的做法是:用明确命名的方法,如
ShallowCopy()和DeepCopy(),避免语义模糊 - 泛型接口如
ICloneable<t></t>(非 BCL 内置)可提升类型安全,但需自行定义和维护
深拷贝真正的复杂点不在“怎么写”,而在“哪些字段值得深拷”——业务逻辑决定哪些引用必须隔离,哪些可以共享。忽略这点,再漂亮的序列化或反射方案,都可能在某个边界 case 下悄悄破坏状态一致性。


















