C#浅拷贝和深拷贝的区别与实现方法
在处理大量数据填充对象时,为避免重复构建开销,常采用对象拷贝技术。本文通过具体代码实例,深入剖析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#对象内存管理的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













