C# 深层副本与浅层副本
数据复制是编程中的重要任务。 对象是 OOP 中的复合数据类型。 对象中的成员字段可以按值或按引用存储。 可以以两种方式执行复制。
浅表副本将所有值和引用复制到新实例中。 引用所指向的数据不会被复制; 仅指针被复制。 新的引用指向原始对象。 对引用成员的任何更改都会影响两个对象。
深层副本将所有值复制到新实例中。 如果成员存储为引用,则深层副本将对正在引用的数据执行深层副本。 创建一个引用对象的新副本。 并存储指向新创建对象的指针。 对这些引用对象的任何更改都不会影响该对象的其他副本。 深拷贝是完全复制的对象。
如果成员字段是值类型,则将对该字段进行逐位复制。 如果该字段是引用类型,则复制引用,但不是复制引用的对象。 因此,原始对象中的引用和克隆对象中的引用指向同一对象。 (来自 programmingcorner.blogspot.com 的明确解释)
C# 浅表复制
以下程序执行浅表复制。
Program.
using System;
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,将大小更改为“大”,然后更改颜色对象的红色部分。
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)。 大小不同(“小”与“大”)。 但是,这两个实例的颜色对象的红色部分相同(255)。 更改克隆对象的成员值(id,大小)不会影响原始对象。 更改引用对象(col)的成员也影响了原始对象。 换句话说,两个对象都引用内存中的同一颜色对象。
C# 深层复制
要更改此行为,我们接下来将做一个深层复制。
Program.
using System;
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)。