17370845950

C#怎么实现深拷贝和浅拷贝 C# ICloneable接口使用方法
浅拷贝仅复制值类型字段和引用类型地址,深拷贝递归复制所有层级;MemberwiseClone是内置浅拷贝方法,JSON序列化是推荐的深拷贝方式,ICloneable接口不区分深浅且已不推荐使用。

在 C# 中,浅拷贝(Shallow Copy)只复制对象的值类型字段和引用类型的地址(即新旧对象共享同一引用对象),而深拷贝(Deep Copy)会递归复制所有层级的对象,确保新对象与原对象完全独立。实现方式有多种,ICloneable 接口是传统方法之一,但需注意它本身不区分深浅,具体行为由你实现决定。

浅拷贝:MemberwiseClone 是最直接的方式

Object.MemberwiseClone() 是 .NET 提供的内置浅拷贝方法,它创建一个新对象,并将当前对象的字段值逐个复制过去。值类型被复制值,引用类型被复制引用(地址)。

  • 只能在类内部调用(因为是 protected 方法)
  • 无需实现接口,开销小,适合简单结构
  • 若类中包含数组、集合或自定义引用类型,它们不会被重新创建,新旧对象仍指向同一实例

示例:

class Person { public string Name; public Address Addr; }
class Address { public string City; }
var p1 = new Person { Name = "Alice", Addr = new Address { City = "Beijing" } };
var p2 = (Person)p1.MemberwiseClone(); // 浅拷贝
p2.Addr.City = "Shanghai"; // p1.Addr.City 也会变成 "Shanghai"

深拷贝:推荐使用序列化或手动克隆

没有通用的“一键深拷贝”机制,常见可靠方式有:

  • JSON 序列化(推荐用于简单 POCO 类):用 System.Text.JsonNewtonsoft.Json 序列化再反序列化,天然实现深拷贝(前提是类型可序列化且无循环引用)
  • 二进制序列化(已过时,不建议新项目使用):依赖 [Serializable],且类型必须标记为可序列化,.NET Core/.NET 5+ 中默认禁用
  • 手动实现 Clone 方法:对每个引用字段显式 new 并复制,控制力最强,适合复杂逻辑或性能敏感场景

JSON 示例(.NET Core 3.0+):

var json = JsonSerializer.Serialize(p1);
var p2 = JsonSerializer.Deserialize(json); // 完全独立的新对象

ICloneable 接口:语义约定,不是语法保障

ICloneable 只定义了一个 Clone() 方法,它不指定是深还是浅——这是开发者责任。很多老代码或文档里把它等同于“浅拷贝”,但实际应以文档或实现为准。

  • 实现时需明确注释说明是深还是浅,否则极易引发 bug
  • 返回类型是 object,调用方需强制转换,不够类型安全
  • .NET 团队已将其标记为“不推荐使用”(obsoleted in .NET 5+ 的某些分析规则中),现代代码更倾向用专用方法如 CloneDeep() 或构造函数传参

示例(显式声明为深拷贝):

class Person : ICloneable {
  public string Name; public Address Addr;
  public object Clone() => new Person {
    Name = this.Name,
    Addr = this.Addr == null ? null : new Address { City = this.Addr.City }
  };
}

实用建议:按场景选方案

  • 只要复制一层字段,且不含复杂引用 → 用 MemberwiseClone
  • 数据简单、可序列化、不追求极致性能 → 用 JsonSerializer 深拷贝
  • 需要精确控制每一步(比如跳过某些字段、处理循环引用、调用自定义初始化)→ 手写克隆逻辑
  • 团队协作或公共库中 → 避免仅靠 ICloneable,改用命名清晰的方法如 With(...)DeepCopy() 或记录类型(record)的 with 表达式

基本上就这些。深浅拷贝本质是对象图遍历策略的选择,关键是理解你的数据结构和共享意图,而不是迷信某个接口或方法名。