浅拷贝仅复制对象第一层属性,引用类型共享内存地址,修改嵌套对象会影响原对象;深拷贝递归复制全部层级,生成完全独立副本。

浅拷贝用 MemberwiseClone() 就够了,但只复制一层引用
默认的 MemberwiseClone() 是浅拷贝,它会为值类型字段创建副本,但对所有引用类型字段只复制引用地址——也就是说,新旧对象的嵌套对象(比如 List、自定义类实例)仍指向同一块内存。
常见错误现象:obj2.Name = "new"; 改了没问题,但 obj2.Items.Add("x"); 后 obj1.Items 也变长了。
适用场景:对象结构扁平、不含嵌套引用类型,或你明确希望共享内部状态(比如缓存句柄、共享配置)。
- 必须把类声明为
public class MyClass : ICloneable才方便统一接口,但不是强制要求 -
MemberwiseClone()是protected方法,只能在本类或子类中调用 - 如果类里有
readonly字段,浅拷贝后它们在新对象里依然不可变,但指向的引用对象仍可被修改
深拷贝别手写递归,优先用 System.Text.Json 序列化反序列化
.NET 5+ 推荐用 System.Text.Json 做深拷贝:先序列化成 JSON 字符串,再反序列化为新对象。它天然跳过引用共享问题,且性能比 BinaryFormatter 更好、更安全。
示例:
var clone = JsonSerializer.Deserialize<myclass>(
JsonSerializer.Serialize(original, new JsonSerializerOptions { WriteIndented = false }));</myclass>
注意点:
- 目标类型必须有无参构造函数;否则反序列化失败,抛出
JsonException - 含循环引用的对象会直接报错:
System.Text.Json.JsonException: A possible object cycle was detected,得提前断开或改用ReferenceHandler.Preserve - 不支持
DateTimeKind.Unspecified在时区处理上的精确还原(默认按本地时区解析),必要时需配JsonSerializerOptions.Converters.Add(new JsonTimeOnlyConverter())类似定制转换器
用 Newtonsoft.Json 要小心默认行为差异
如果你还在用 Newtonsoft.Json(即 Json.NET),它的 JsonConvert.SerializeObject() 默认允许循环引用(靠 $id/$ref 处理),但深拷贝时反而容易“意外保留引用”——尤其当你没关 PreserveReferencesHandling。
正确做法是显式关闭引用追踪:
var settings = new JsonSerializerSettings {
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
PreserveReferencesHandling = PreserveReferencesHandling.None
};
var clone = JsonConvert.DeserializeObject<myclass>(JsonConvert.SerializeObject(original, settings), settings);</myclass>
容易踩的坑:
- 漏设
PreserveReferencesHandling = None→ 拷贝后两个对象的某个子对象仍是同一个实例 - 类里有
[JsonIgnore]或[NonSerialized]→ 对应字段不会被拷贝,但你可能根本没意识到它被跳过了 - 含
IDisposable成员(如FileStream)→ 序列化会失败,得提前剥离或用自定义JsonConverter
复杂对象含非序列化成员?手动实现 ICloneable + 逐字段控制
当对象含文件句柄、数据库连接、委托、指针、ThreadStatic 字段等无法序列化的成员时,必须手动深拷贝。这时不要依赖通用序列化方案,而是实现 ICloneable 并精细控制每个字段。
关键原则:
- 值类型字段直接赋值;引用类型字段调用其
Clone()或新建实例并复制内容(如new List)(originalList) - 对只读集合(
IReadOnlyList)要转成可变类型再初始化,否则new List才能真正隔离((IEnumerable )roList) - 含事件字段(
event Action x;)要忽略——不能也不该拷贝委托链,否则引发多播异常或内存泄漏 - 如果类本身是继承体系,记得在子类
Clone()中调用base.Clone()并修正类型返回值(用as DerivedClass或泛型约束)
最易被忽略的一点:深拷贝后,原对象和克隆体若都持有对同一外部资源(如临时文件路径、内存映射视图名)的逻辑引用,运行时可能冲突——这不是拷贝逻辑的问题,而是设计上没考虑资源所有权转移。











