C# 浅拷贝和深拷贝的实现

拷贝(复制)为对象创建副本,即将对象中的所有字段复制到新的对象(副本中)。拷贝有两种:浅拷贝和深拷贝,微软建议用类型继承ICloneable接口的方式明确该类型是可以被拷贝的,ICloneable接口只提供了一个Clone方法,需要根据需要在Clone方法内实现浅拷贝或深拷贝。

1、浅拷贝:把源对象中的值类型字段的引用类型字段的引用复制到副本中。在源对象(副本)中,修改值类型字段的值不会影响到副本(源对象),而修改引用类型字段的值会影响到副本(源对象)。

注意string类型除外,虽然string类型是引用类型,但是由于该引用类型的特殊性,在浅拷贝过程,副本中会创建新的字符串并把对应的值复制过来,字符串应被看成值类型。

浅拷贝声明代码,使用Object.MeberwiseClone方法进行浅拷贝:

 1     class Employee : ICloneable
 2     {
 3         public string ID { get; set; }
 4         public int Age { get; set; }
 5         public Department DepartmentName { get; set; }
 6 
 7         //实现ICloneable接口的Clone方法
 8         public object Clone()
 9         {
10             return this.MemberwiseClone();//浅拷贝
11         }
12     }
13     class Department
14     {
15         public string DepartmentName { get; set; }
16         public Department(string value)
17         {
18             DepartmentName = value;
19         }
20         public override string ToString()
21         {
22             return DepartmentName.ToString();
23         }
24     }

调用浅拷贝代码:

 1      Employee emp1 = new Employee()
 2      {
 3          ID = "NO1",
 4          Age = 20,
 5          DepartmentName = new Department("Technology")
 6      };
 7      Employee emp2 = emp1.Clone() as Employee;//浅拷贝
 8 
 9      Console.WriteLine("-------初始化赋值------");
10      Console.WriteLine(string.Format("[emp1] id:{0}\tage:{1}\tdepartment:{2}", emp1.ID, emp1.Age, emp1.DepartmentName));
11      Console.WriteLine(string.Format("[emp2] id:{0}\tage:{1}\tdepartment:{2}", emp2.ID, emp2.Age, emp2.DepartmentName));
12 
13      Console.WriteLine("\n-------改变emp1的值-------");
14      emp1.ID = "NO2";
15      emp1.Age = 22;
16      emp1.DepartmentName.DepartmentName = "sales";
17      Console.WriteLine(string.Format("[emp1] id:{0}\tage:{1}\tdepartment:{2}", emp1.ID, emp1.Age, emp1.DepartmentName));
18      Console.WriteLine(string.Format("[emp2] id:{0}\tage:{1}\tdepartment:{2}", emp2.ID, emp2.Age, emp2.DepartmentName));
19 
20      Console.WriteLine("\n-------改变emp2的值-------");
21      emp2.ID = "NO3";
22      emp2.Age = 24;
23      emp2.DepartmentName.DepartmentName = "personnel";
24      Console.WriteLine(string.Format("[emp1] id:{0}\tage:{1}\tdepartment:{2}", emp1.ID, emp1.Age, emp1.DepartmentName));
25      Console.WriteLine(string.Format("[emp2] id:{0}\tage:{1}\tdepartment:{2}", emp2.ID, emp2.Age, emp2.DepartmentName));

运行结果:

-------初始化赋值------
[emp1] id:NO1   age:20  department:Technology
[emp2] id:NO1   age:20  department:Technology

-------改变emp1的值-------
[emp1] id:NO2   age:22  department:sales
[emp2] id:NO1   age:20  department:sales

-------改变emp2的值-------
[emp1] id:NO2   age:22  department:personnel
[emp2] id:NO3   age:24  department:personnel

从结果可以看出,Age是值类型,ID是string类型这里被当做值类型处理,所以ID和Age修改了对另一个对象没有影响;Department属性是引用类型,浅拷贝emp1和emp2引用的是同一个Department对象,其中一个修改了DepartmentName的值会影响另一个。

 

2、深拷贝:把源对象的值类型字段和引用类型字段,重新创建并赋值。在源对象(副本)中,修改值类型字段的值或者引用类型字段的值都不会影响到副本(源对象)。

建议使用序列化的形式来进行深拷贝

深拷贝代码:

    [Serializable]//标记可序列化
    class Employee : ICloneable
    {
        public string ID { get; set; }
        public int Age { get; set; }
        public Department DepartmentName { get; set; }

        //实现ICloneable接口的Clone方法
        public object Clone()
        {
            using (MemoryStream stream = new MemoryStream())
            {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(stream, this);
                stream.Seek(0, SeekOrigin.Begin);
                return formatter.Deserialize(stream) as Employee;
            }
        }
    }

    [Serializable]//标记可序列化
    class Department
    {
        public string DepartmentName { get; set; }
        public Department(string value)
        {
            DepartmentName = value;
        }
        public override string ToString()
        {
            return DepartmentName.ToString();
        }
    }

跟上面调用浅拷贝代码一样调用深拷贝,运行结果:

-------初始化赋值------
[emp1] id:NO1   age:20  department:Technology
[emp2] id:NO1   age:20  department:Technology

-------改变emp1的值-------
[emp1] id:NO2   age:22  department:sales
[emp2] id:NO1   age:20  department:Technology

-------改变emp2的值-------
[emp1] id:NO2   age:22  department:sales
[emp2] id:NO3   age:24  department:personnel

拷贝以后,无论是修改值类型还是引用类型,都对另一个对象没有影响。

3、要同时实现深拷贝和浅拷贝,可以在Clone方法外,额外实现两个方法,声明为DeepClone和Shallow:

 1     [Serializable]//标记可序列化
 2     class Employee : ICloneable
 3     {
 4         public string ID { get; set; }
 5         public int Age { get; set; }
 6         public Department DepartmentName { get; set; }
 7 
 8         //实现ICloneable接口的Clone方法
 9         public object Clone()
10         {
11             return this.MemberwiseClone();
12         }
13 
14         //深拷贝
15         public Employee DeepClone()
16         {
17             using (MemoryStream stream = new MemoryStream())
18             {
19                 BinaryFormatter formatter = new BinaryFormatter();
20                 formatter.Serialize(stream, this);
21                 stream.Seek(0, SeekOrigin.Begin);
22                 return formatter.Deserialize(stream) as Employee;
23             }
24         }
25 
26         //浅拷贝
27         public Employee Shallow()
28         {
29             return this.Clone() as Employee;
30         }
31     }

 

参考:《编写高质量代码改善C#程序的157个建议》陆敏技

C# 浅拷贝和深拷贝的实现

上一篇:AcWing 1490. 最长上升子串 模拟优化


下一篇:C# 为类型输出格式化字符串