浅拷贝用memberwiseclone()即可,深拷贝优先用system.text.json序列化反序列化;前者仅复制字段引用,后者需确保类型可序列化、无循环引用及不可序列化成员。

直接说结论:浅拷贝用 MemberwiseClone() 就够了,深拷贝别手写递归,优先走 System.Text.Json 序列化反序列化,但必须确认类型可序列化、无循环引用、无不可序列化成员(如 FileStream、Delegate、event)。
浅拷贝为什么只用 MemberwiseClone()?
它不依赖反射、不触发构造函数、不读取属性 setter、不关心字段是否 readonly 或 private,就是纯内存位拷贝第一层字段——值类型真复制,引用类型只复制地址。
常见错误现象:obj2.Items.Add(x) 后 obj1.Items 也变了,就是因为 Items 是 List<t></t>,浅拷贝没新建列表,只是复制了指向同一实例的引用。
适用场景:
- 对象结构扁平,不含嵌套引用类型(比如只有
string、int、DateTime) - 你明确希望共享内部状态(如共用一个
ILogger实例或缓存句柄) - 性能敏感,不能接受序列化开销
注意点:
-
MemberwiseClone()是protected方法,只能在本类或子类中调用 - 类必须是
class,struct赋值本身就是值拷贝,无需调用 -
event字段会被复制订阅列表引用——副本触发事件,原对象也会响应,这是隐蔽 bug 来源 -
string虽是引用类型,但因不可变性,浅拷贝行为等同于值类型,不用额外处理
深拷贝为什么首选 System.Text.Json?
它天然绕过引用共享问题,且比 BinaryFormatter 安全、比 Newtonsoft.Json 更快、是 .NET 5+ 官方主推方案。只要你的类是标准 POCO(public 读写属性、无参构造、无不可序列化字段),一行代码就能搞定:
var clone = JsonSerializer.Deserialize<myclass>(JsonSerializer.Serialize(original));</myclass>
但容易踩的坑远多于表面看起来:
- 目标类型必须有 public 无参构造函数,否则抛
JsonException - 含循环引用(如父子双向引用)会直接报错:
A possible object cycle was detected;需提前断开,或用ReferenceHandler.Preserve(但会引入$id/$ref,不是纯深拷贝语义) -
DateTimeKind.Unspecified时区信息可能丢失,默认按本地时区解析;必要时得加自定义 converter(如JsonDateTimeConverter) - 默认忽略
private字段和readonly字段;.NET 6+ 可设JsonSerializerOptions.IncludeFields = true,但依然不支持只读自动属性(get => _field) -
Dictionary<k></k>、TimeSpan、DBNull等类型需确认JsonSerializerOptions配置兼容(例如PropertyNameCaseInsensitive = true)
什么时候必须手动实现深拷贝?
当你遇到以下任意一种情况时,序列化方案基本失效,只能手写:
- 对象含
FileStream、Thread、DbContext、Expression、Delegate、IDisposable实现等不可序列化成员 - 需要保留私有字段状态、只读集合内容、
DateTimeKind精度、或委托绑定上下文 - 性能要求极高,无法承受 JSON 序列化/反序列化的字符串分配与解析开销
手动实现的关键不是“全字段递归”,而是精准控制:
- 只对「可能被修改的引用字段」做深拷贝,比如
List<t></t>→new List<t>(original)</t>,Dictionary<k></k>→new Dictionary<k>(original)</k> - 跳过
static、readonly、event字段——它们本就不该参与拷贝 - 对嵌套对象,调用其自有
DeepClone()方法(如有),而非统一走反射 - 用
HashSet<object></object>缓存已处理对象,防止无限递归(尤其处理循环引用时)
为什么 ICloneable 和 BinaryFormatter 基本可以弃用了?
ICloneable.Clone() 接口没有任何契约约束——它既可能是浅拷贝,也可能是深拷贝,调用方完全无法预期行为,实际项目中几乎没人靠它做统一克隆入口。
BinaryFormatter 在 .NET 5+ 已标记为过时,.NET 6+ 默认禁用,存在严重反序列化漏洞,且遇到 Stream、Thread、DbContext 等类型直接抛 SerializationException,连编译通过都不代表能跑通。
真正复杂的地方从来不是“怎么写 Clone 方法”,而是「哪些字段该深、哪些该跳、哪些该复用、哪些根本不能碰」——这需要你对业务模型和对象生命周期有清晰判断,而不是套个序列化模板就完事。











