C# 淺拷貝和深拷貝的實現
阿新 • • 發佈:2020-06-22
拷貝(複製)為物件建立副本,即將物件中的所有欄位複製到新的物件(副本中)。拷貝有兩種:淺拷貝和深拷貝,微軟建議用型別繼承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 overridestring 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個建議》陸敏技