1. 程式人生 > WINDOWS開發 >C# 淺拷貝和深拷貝的實現

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.DepartmentName));
18      Console.WriteLine(string.Format("[emp2] id:{0}\tage:{1}\tdepartment:{2}",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.DepartmentName));
25      Console.WriteLine(string.Format("[emp2] id:{0}\tage:{1}\tdepartment:{2}",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個建議》陸敏技