C# 浅拷贝与深拷贝
最后修改于 2023 年 7 月 5 日
在本文中,我们比较 C# 中对象的浅拷贝和深拷贝。
数据复制是编程中的一项重要任务。 对象是 OOP 中的一种复合数据类型。 对象中的成员字段可以按值或按引用存储。 复制可以通过两种方式进行。
浅拷贝将所有值和引用复制到新实例中。 指针指向的数据不会被复制; 仅复制指针。 新的引用指向原始对象。 对引用成员的任何更改都会影响这两个对象。
深拷贝将所有值复制到新实例中。 对于存储为引用的成员,深拷贝会对正在引用的数据执行深拷贝。 创建被引用对象的新副本。 并且存储指向新创建对象的指针。 对这些引用对象的任何更改都不会影响对象的其他副本。 深拷贝是完全复制的对象。
如果成员字段是值类型,则执行该字段的逐位复制。 如果该字段是引用类型,则复制引用,但不复制被引用的对象; 因此,原始对象中的引用和克隆中的引用指向同一个对象。
C# 浅拷贝示例
以下程序执行浅拷贝。
namespace ShallowCopy; class Color { public int red; public int green; public int blue; public Color(int red, int green, int blue) { this.red = red; this.green = green; this.blue = blue; } } class MyObject : ICloneable { public int id; public string size; public Color col; public MyObject(int id, string size, Color col) { this.id = id; this.size = size; this.col = col; } public object Clone() { return new MyObject(this.id, this.size, this.col); } public override string ToString() { var s = String.Format("id: {0}, size: {1}, color:({2}, {3}, {4})", this.id, this.size, this.col.red, this.col.green, this.col.blue); return s; } } class Program { static void Main(string[] args) { var col = new Color(23, 42, 223); var obj1 = new MyObject(23, "small", col); var obj2 = (MyObject)obj1.Clone(); obj2.id += 1; obj2.size = "big"; obj2.col.red = 255; Console.WriteLine(obj1); Console.WriteLine(obj2); } }
这是一个浅拷贝的示例。 我们定义两个自定义对象:MyObject
和 Color
。 MyObject
对象将引用 Color 对象。
class MyObject : ICloneable
对于我们要克隆的对象,我们应该实现 ICloneable
接口。
public object Clone() { return new MyObject(this.id, this.size, this.col); }
ICloneable
接口强制我们创建一个 Clone
方法。 此方法返回一个带有复制值的新对象。
var col = new Color(23, 42, 223);
我们创建一个 Color 对象的实例。
var obj1 = new MyObject(23, "small", col);
创建 MyObject
类的实例。 Color
对象的实例被传递给构造函数。
var obj2 = (MyObject) obj1.Clone();
我们创建 obj1 对象的浅拷贝并将其分配给 obj2 变量。 Clone
方法返回一个 Object
,我们期望的是 MyObject
。 这就是我们进行显式转换的原因。
obj2.id += 1; obj2.size = "big"; obj2.col.red = 255;
在这里,我们修改复制对象的成员字段。 我们增加 id,将大小更改为“big”,并更改颜色对象的红色部分。
Console.WriteLine(obj1); Console.WriteLine(obj2);
Console.WriteLine
方法调用 obj2
对象的 ToString
方法,该方法返回对象的字符串表示形式。
$ dotnet run id: 23, size: small, color:(255, 42, 223) id: 24, size: big, color:(255, 42, 223)
我们可以看到 id 不同(23 与 24)。 大小不同(“small”与“big”)。 但是颜色对象的红色部分对于两个实例是相同的(255)。 更改克隆对象的成员值(id、大小)不会影响原始对象。 更改引用对象 (col) 的成员也会影响原始对象。 换句话说,两个对象都引用内存中的同一个颜色对象。
C# 深拷贝示例
为了改变这种行为,我们接下来进行深拷贝。
namespace DeepCopy; class Color : ICloneable { public int red; public int green; public int blue; public Color(int red, int green, int blue) { this.red = red; this.green = green; this.blue = blue; } public object Clone() { return new Color(this.red, this.green, this.blue); } } class MyObject : ICloneable { public int id; public string size; public Color col; public MyObject(int id, string size, Color col) { this.id = id; this.size = size; this.col = col; } public object Clone() { return new MyObject(this.id, this.size, (Color)this.col.Clone()); } public override string ToString() { var s = String.Format("id: {0}, size: {1}, color:({2}, {3}, {4})", this.id, this.size, this.col.red, this.col.green, this.col.blue); return s; } } class Program { static void Main(string[] args) { var col = new Color(23, 42, 223); var obj1 = new MyObject(23, "small", col); var obj2 = (MyObject)obj1.Clone(); obj2.id += 1; obj2.size = "big"; obj2.col.red = 255; Console.WriteLine(obj1); Console.WriteLine(obj2); } }
在这个程序中,我们对一个对象执行深拷贝。
class Color : ICloneable
现在 Color 类实现了 ICloneable
接口。
public object Clone() { return new Color(this.red, this.green, this.blue); }
我们也有 Color
类的 Clone
方法。 这有助于创建引用对象的副本。
public object Clone() { return new MyObject(this.id, this.size, (Color) this.col.Clone()); }
当我们克隆 MyObject
时,我们会在 col 引用类型上调用 Clone
方法。 这样我们也有了一个颜色值的副本。
$ dotnet run id: 23, size: small, color:(23, 42, 223) id: 24, size: big, color:(255, 42, 223)
现在引用 Color 对象的红色部分不一样了。 原始对象保留了其先前的值 (23)。
来源
在本文中,我们比较了 C# 中对象的浅拷贝和深拷贝。
作者
列出所有 C# 教程。