C#浅拷贝和深拷贝的区别与实现方法

纸上星河
纸上星河
发布时间:2026-10-14| 2人浏览过
原创

在处理大量数据填充对象时,为避免重复构建开销,常采用对象拷贝技术。本文通过具体代码实例,深入剖析C#中浅拷贝(Shallow Copy)与深拷贝(Deep Copy)的原理差异及实现方式,重点展示引用类型成员在两种拷贝模式下对内存实例的影响。

结果与目标

在C#开发中,对象拷贝的核心在于处理值类型(如Struct、基本数据类型)和引用类型(如Class)的内存分配。目标是掌握以下两种拷贝方式的本质区别:

  • 浅拷贝:值类型成员独立复制,引用类型成员仅复制引用地址,副本与原对象共享堆上的同一实例。
  • 深拷贝:不仅复制值类型,还在托管堆上创建引用类型的新实例,确保原对象与副本完全隔离。

准备条件:定义测试实体

为了直观展示拷贝效果,我们定义三个核心类:包含引用类型的`Room`、包含值类型的`Film`结构体,以及组合了前两者的`Cinema`类。

public class Room
{
  public int _maxSeat;

  public Room(int maxSeat)
  {
    this._maxSeat = maxSeat;
  }
}

public struct Film
{
  public string _name;

  public Film(string name)
  {
    this._name = name;
  }
}

public class Cinema
{
  public Room _room; // 引用类型
  public Film _film; // 值类型

  public Cinema(Room room, Film film)
  {
    this._room = room;
    this._film = film;
  }
}

第1步:实现并验证浅拷贝

浅拷贝可通过内置方法快速实现,但其行为往往容易引发潜在Bug。

步骤1.1:使用 MemberwiseClone 进行浅复制

在`Cinema`类中重写`Clone`方法,调用基类的`MemberwiseClone()`。

public object Clone()
{
  return MemberwiseClone(); 
}

步骤1.2:执行测试与观察

创建`cinema1`对象并拷贝为`cinema2`。随后修改`cinema1`中的值类型字段和引用类型字段,观察两个对象的输出变化。关键点在于修改引用类型成员(如`_room`的状态)时,因为两者指向同一堆内存地址,`cinema2`的值也会随之改变。

static void Main(string[] args)
{
  Room room1 = new Room(60);
  Film film1 = new Film("家园防线");
  Cinema cinema1 = new Cinema(room1, film1);
  Cinema cinema2 = (Cinema)cinema1.Clone();

  Console.WriteLine("拷贝之前: {0}, {1}", cinema1._film._name, cinema1._room._maxSeat);
  Console.WriteLine("拷贝之后: {0}, {1}", cinema2._film._name, cinema2._room._maxSeat);

  // 修改原对象
  cinema1._film._name = "极品飞车"; // 值类型修改
  cinema1._room._maxSeat = 80;      // 引用类型属性修改

  Console.WriteLine("修改后原对象: {0}, {1}", cinema1._film._name, cinema1._room._maxSeat);
  Console.WriteLine("修改后副本对象: {0}, {1}", cinema2._film._name, cinema2._room._maxSeat);

  Console.ReadKey();
}

运行结果如下:

分析:
从结果可见,修改原对象`cinema1`的引用成员`_room._maxSeat`后,副本`cinema2`对应的数值也变成了80。这证实了浅拷贝只复制了引用地址,导致两个对象操作的是同一块内存区域。

第2步:实现并验证深拷贝

深拷贝旨在创建物理上独立的副本,适用于需要完全隔离状态的场景。

步骤2.1:手动逐成员复制实现深拷贝

这是最稳妥的方式。在新建对象时,重新实例化引用类型成员,并为值类型成员赋予新值。

public object Clone()
{
  // 重新创建引用类型的实例
  Room room = new Room(this._room._maxSeat);
  // 值类型直接赋值即可
  Film film = this._film;
  // 传入全新的引用类型实例
  return new Cinema(room, film);
}

步骤2.2:使用序列化实现深拷贝

对于复杂对象图,通过二进制序列化和反序列化是常见的捷径。但要求所有涉及的类必须标记为可序列化。

// 必须先给类添加 [Serializable] 特性
[Serializable]
public class Room { ... }
[Serializable]
public struct Film { ... }
[Serializable]
public class Cinema { ... }

public object Clone1()
{
  BinaryFormatter bf = new BinaryFormatter();
  MemoryStream ms = new MemoryStream();
  bf.Serialize(ms, this); 
  ms.Position = 0;
  return (bf.Deserialize(ms));
}

步骤2.3:执行测试与观察

使用上述任一种深拷贝方法生成副本,再次进行同样的修改操作。

Cinema cinema2 = (Cinema)cinema1.Clone1();
// ... 修改 cinema1 ...
Console.WriteLine("修改后副本对象: {0}, {1}", cinema2._film._name, cinema2._room._maxSeat); 

结果:

分析:
如图所示,修改原对象`cinema1`后,副本`cinema2`的值依然保持初始状态(Name不变,Seat仍为60)。说明深拷贝在堆上创建了新的`Room`实例,两者已彻底分离,互不影响。

简要总结

选择拷贝方式取决于业务需求:若仅需快照且性能敏感,浅拷贝足够;若涉及多线程或需保证数据绝对独立,务必使用深拷贝。以上就是C#浅拷贝和深拷贝实例解析的详细内容,更多关于C#对象内存管理的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。