建議14: 正確實現淺拷貝和深拷貝
為對象創建副本的技術稱為拷貝(也叫克隆)。我們將拷貝分為淺拷貝和深拷貝。
無論是淺拷貝還是深拷貝,微軟都建議用類型繼承ICloneable接口的方式明確告訴調用者:該類型可以被拷貝。當然,ICloneable接口 只提供了一個聲明為Clone的方法,我們可以根據需求在Clone方法內實現淺拷貝或深拷貝。一個簡單的淺拷貝的實現代碼如下所示:
class Employee : ICloneable { public string IDCode { get; set; } public int Age { get; set; } public Department Department { get; set; } #region ICloneable 成員 public object Clone() { return this.MemberwiseClone(); } #endregion } class Department { public string Name { get; set; } public override string ToString() { return this.Name; } }
調用方代碼如下所示:
Employee mike = new Employee() { IDCode = "NB123", Age = 30, Department = new Department() { Name = "Dep1" } }; Employee rose = mike.Clone() as Employee; Console.WriteLine(rose.IDCode); Console.WriteLine(rose.Age); Console.WriteLine(rose.Department); Console.WriteLine("開始改變mike的值:"); mike.IDCode = "NB456"; mike.Age = 60; mike.Department.Name = "Dep2"; Console.WriteLine(rose.IDCode); Console.WriteLine(rose.Age); Console.WriteLine(rose.Department);
輸出為:
NB123 30 Dep1 開始改變mike的值:NB123 30 Dep2
注意到Employee的IDCode屬性是string類型。理論上string類型是引用類型,但是由于該引用類型的特殊性(無論是實現還是語義),Object.MemberwiseClone方法仍舊為其創建了副本。也就是說,在淺拷貝過程,我們應該將字符串看成是值類型。 Employee的Department屬性是一個引用類型,所以,如果改變了源對象mike中的值,副本rose中的值也會隨之一起變動。 Employee的深拷貝有多種實現方法,最簡單的方法是手動對字段逐個進行賦值。但這種方法容易出錯,也就是說,如果類型的字段發生變化或有增減,那么 該拷貝方法也要發生相應的變化,所以,建議使用序列化的形式來進行深拷貝。Employee深拷貝的一個簡單實現代碼如下所示:
class Employee : ICloneable { public string IDCode { get; set; } public int Age { get; set; } public Department Department { get; set; } #region ICloneable 成員 public object Clone() { using (Stream objectStream = new MemoryStream()) { IFormatter formatter = new BinaryFormatter(); formatter.Serialize(objectStream, this); objectStream.Seek(0, SeekOrigin.Begin); return formatter.Deserialize(objectStream) as Employee; } } #endregion }
使用淺拷貝中的調用者代碼,輸出為:
NB123 30 Dep1 開始改變mike的值:NB123 30 Dep1
可以發現,再次更改mike的值已經不會影響副本rose的值了。 由于接口ICloneable只有一個模棱兩可的Clone方法,所以,如果要在一個類中同時實現深拷貝和淺拷貝,只能由我們自己實現兩個額外的方法,聲明為DeepClone和Shallow。Employee的最終版本看起來應該像如下的形式:
[Serializable] class Employee : ICloneable { public string IDCode { get; set; } public int Age { get; set; } public Department Department { get; set; } #region ICloneable 成員 public object Clone() { return this.MemberwiseClone(); } #endregion public Employee DeepClone() { using (Stream objectStream = new MemoryStream()) { IFormatter formatter = new BinaryFormatter(); formatter.Serialize(objectStream, this); objectStream.Seek(0, SeekOrigin.Begin); return formatter.Deserialize(objectStream) as Employee; } } public Employee ShallowClone() { return Clone() as Employee; } }
轉自:《編寫高質量代碼改善C#程序的157個建議》陸敏技
新聞熱點
疑難解答