C# 深层副本与浅层副本 深拷贝与浅拷贝

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);
        }
    }
}

这是一个浅表副本的示例。 我们定义了两个自定义对象:MyObjectColor。 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)。

### 如何深浅拷贝 Java 和 C# 实体类属性值 #### 浅拷贝深拷贝的概念 浅拷贝仅复制对象中的基本数据类型以及引用类型的地址,而不会创建新的对象实例。对于引用类型的数据成员,源对象和目标对象共享同一份内存空间[^1]。 深拷贝不仅会复制对象本身及其内部的基本数据类型字段,还会递归地为每一个引用类型的字段分配新内存并赋值,从而确保原对象副本之间没有任何关联。 #### Java 中的浅拷贝深拷贝 ##### 浅拷贝实现方式: 可以通过继承 `Cloneable` 接口并重写 `clone()` 方法来完成浅拷贝操作,在此过程中需要注意调用父类的 `super.clone()` 函数以获得当前对象的一个副本。 ```java public class Person implements Cloneable { private String name; private Address address; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } ``` ##### 深拷贝实现方式: 为了达到真正的深拷贝效果,除了要克隆原始对象外,还需要手动处理所有的非基础数据类型的成员变量,即对这些成员也执行相应的克隆逻辑。 ```java @Override protected Person clone() throws CloneNotSupportedException { Person clonedPerson = (Person) super.clone(); clonedPerson.address = this.address != null ? this.address.clone() : null; return clonedPerson; } ``` 如果不想自己编写复杂的克隆代码,则可以考虑使用序列化机制来进行深拷贝。具体做法是先将对象转换成字节数组保存到临时文件或者内存缓冲区中,再反序列化得到一个新的对象实例。 ```java // 序列化深拷贝 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return (Person) ois.readObject(); ``` #### C# 中的浅拷贝深拷贝 C# 提供了两种简单的途径用于实现浅拷贝——通过 `MemberwiseClone()` 方法或是利用反射技术;而对于深拷贝而言,通常推荐采用 JSON 或 XML 的序列化/反序列化的手段。 ##### 使用 MemberwiseClone 进行浅拷贝: ```csharp public class Employee : ICloneable { public string Name { get; set; } public Department Dept { get; set; } public object Clone() { return this.MemberwiseClone(); // 创建该对象的一份浅表副本 } } ``` ##### 利用 Newtonsoft.Json 实现深拷贝: ```csharp using Newtonsoft.Json; public static T DeepCopy<T>(T obj) { var serialized = JsonConvert.SerializeObject(obj); return JsonConvert.DeserializeObject<T>(serialized); } var originalEmployee = new Employee { /* 初始化 */ }; var copiedEmployee = DeepCopy(originalEmployee); // 执行深拷贝 ``` #### 最佳实践建议 - 对于简单结构的对象可以选择直接覆盖默认的 `clone()` 方法或使用 `MemberwiseClone()` 来快速构建浅层副本; - 如果涉及到复杂嵌套关系或者是含有不可变集合的情况时,应该优先选用基于序列化的方案来做深层复制工作; - 需要注意的是无论哪种方法都可能带来性能开销上的差异,因此实际应用当中应当权衡利弊选取最适合的方式。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值