MemberwiseClone仅支持浅拷贝,复制引用类型时共享内存地址;深拷贝推荐用JsonSerializer.Serialize/Deserialize,需注意循环引用、不可序列化字段及类型兼容性。

用 MemberwiseClone 只能得到浅拷贝,别误当深拷贝用
很多刚接触 C# 的人看到 MemberwiseClone 就以为能复制整个对象树,结果改了副本里的集合或引用类型字段,原对象跟着变。这是因为 MemberwiseClone 只复制字段值:对值类型是真复制,对引用类型只是复制了地址——两个对象还指着同一块内存。
- 适用场景:仅当对象全是值类型,或你明确知道所有引用字段都不需要隔离时才安全
- 常见错误现象:
obj2.Name = "new";没问题,但obj2.Items.Add(1);会同时出现在obj1.Items里 - 它不递归处理任何嵌套对象、数组、
List<T>、Dictionary<K,V>等
手动实现 ICloneable 容易漏字段,尤其带循环引用时直接栈溢出
自己写 Clone 方法看似可控,但实际项目里实体类常有十几二十个属性,还有嵌套类、可空类型、集合初始化逻辑,一不小心就漏掉某个 new List<T>(src.List) 或忘了处理 DateTime? 的 null 判断。
- 容易踩的坑:
- 忽略
readonly字段(构造后无法赋值) - 没检查
null就直接调.Clone(),抛NullReferenceException - 父子对象互相引用(A.Child = B, B.Parent = A),递归克隆无限深入
- 忽略
- 性能影响:每次都要 new 所有子对象,GC 压力比序列化方案略低,但开发成本高、维护难
用 JsonSerializer.Serialize + Deserialize 是最省心的通用方案
只要类能被 JSON 序列化(无循环引用、无不可序列化字段如 Stream、IntPtr、委托),这个方法几乎零配置就能拿到真正的深拷贝,且 .NET 6+ 的 System.Text.Json 性能足够好。
- 实操建议:
- 加
[JsonIgnore]标记不想复制的字段(比如缓存、上下文) - 遇到
DateTimeOffset或自定义类型,配JsonSerializerOptions加入对应转换器 - 如果类里有
IDisposable成员(如FileStream),别用这法子——JSON 本来就不该序列化它们
- 加
- 简短示例:
var options = new JsonSerializerOptions { WriteIndented = false }; string json = JsonSerializer.Serialize(original, options); T clone = JsonSerializer.Deserialize<T>(json, options);
第三方库 Newtonsoft.Json 在老项目里更兼容,但要注意默认行为差异
如果你还在用 .NET Framework 或依赖大量 JsonProperty 特性,Newtonsoft.Json 的 JsonConvert.SerializeObject 更稳妥。但它默认把 DateTime 序列化成带时区的字符串,而 System.Text.Json 默认是 ISO 8601 简化格式;另外它的循环引用检测开关是 ReferenceLoopHandling.Ignore,不是默认开的。
- 关键区别:
-
Newtonsoft.Json默认允许循环引用(不报错但可能无限循环),System.Text.Json默认直接抛JsonException - 它对
private set属性支持更好,System.Text.Json需要额外配JsonInclude - 反序列化时,
Newtonsoft.Json能自动处理int?接收null或数字,而System.Text.Json在某些版本里需要AllowTrailingCommas等容错选项
-
深拷贝真正麻烦的从来不是“怎么写一行代码”,而是想清楚哪些字段不该复制、哪些引用必须切断、以及要不要保留原始对象的“身份语义”。哪怕用了 JSON 方案,也得盯着日志看有没有意外序列化的敏感字段。










