從今天開始,我將每天和大家分享這本書中的內容,並加上自己的理解,希望可以幫助到更多和我一樣盲目的程序員們。 今天我們談談C#中的對象拷貝問題; 所謂的對象拷貝,其實就是為對象創建副本,C#中將拷貝分為兩種,分別為淺拷貝和深拷貝; 所謂淺拷貝就是將對象中的所有字段復制到新的副本對象中;淺拷貝對於值類型與引用類型的方式有區別,值類型字段的值被復制到副本中後,在副本中的修改不會影響源對象對應的值;然而對於引用類型的字段被復制到副本中的卻是引用類型的引用,而不是引用的對象,在副本中對引用類型的字段值被修改後,源對象的值也將被修改。 深拷貝也同樣是將對象中的所有字段復制到副本對象中,但是,無論對象的值類型字段或者引用類型字段,都會被重新創建並復制,對於副本的修改,不會影響到源對象的本身; 當然,無論是哪種拷貝,微軟都建議使用類型繼承ICloneable接口的方式明確告訴調用者,該對象是否可用被拷貝。當然了,ICloneable接口只提供了一個聲明為Clone的方法,我們可以根據需求在Clone的方法內實現淺拷貝或者是深拷貝,下面我們進行一段淺拷貝的案例,代碼如下 復制代碼 1 class Student : ICloneable 2 { 3 public string IDCode { get; set; } 4 5 public int Age { get; set; } 6 7 public Grent Grent { get; set; } 8 9 #region 拷貝主體 10 public object Clone() 11 { 12 return this.MemberwiseClone(); 13 //throw new NotImplementedException(); 14 } 15 #endregion 16 17 } 18 19 class Grent 20 { 21 public string Name { get; set; } 22 23 public override string ToString() 24 { 25 return this.Name; 26 } 27 } 復制代碼 調用 復制代碼 Student stu1 = new Student() { IDCode = "lily", Age = 24, Grent = new Grent() { Name="五年三班"} }; Student stu2 = stu1.Clone() as Student; if (stu2 == null) { Console.WriteLine("轉換失敗"); Console.ReadKey(); return; } Console.WriteLine(stu2.IDCode); Console.WriteLine(stu2.Age); Console.WriteLine(stu2.Grent.ToString()); Console.WriteLine("重新為stu1賦值"); stu1.IDCode = "Anagle"; stu1.Age += 10; stu1.Grent.Name = "六年二班"; Console.WriteLine(stu2.IDCode); Console.WriteLine(stu2.Age); Console.WriteLine(stu2.Grent.ToString()); Console.ReadKey(); 復制代碼 輸出結果為: lily 24 五年三班 重新為stu1賦值 lily 24 六年二班 這裡我們需要注意一點Student中的IDCode屬性是string類型,理論上string類型是引用類型,但是由於該引用類型的特殊性,Object.MemberwiseClone方法仍舊為他創建了副本,也就是說,在淺拷貝過程中,我們應該將字符串看成值類型; 因為Student中的Grent是引用類型,所以在stu1中的Grent的Name被改變的同時,副本stu2中的Grent的Name也同樣被改變。 Student的深拷貝有多鐘實現方法,最簡單的方法是手動對字段诼個賦值,但是這種方法容易出錯,也就是說,如果類型的字段發生變化或有增減時,那麼該拷貝方法也就要發生相應的變化,所以,建議使用序列化的形式來進行深拷貝。實現代碼如下 復制代碼 [Serializable] class Student : ICloneable { public string IDCode { get; set; } public int Age { get; set; } public Grent Grent { get; set; } #region 拷貝主體 /// <summary> /// 深度拷貝 /// </summary> /// <returns></returns> public Student DeepClone() { using (Stream objectStream = new MemoryStream()) { IFormatter formatter = new BinaryFormatter(); formatter.Serialize(objectStream, this); objectStream.Seek(0,SeekOrigin.Begin); return formatter.Deserialize(objectStream) as Student; } } public object Clone() { return this.MemberwiseClone(); } #endregion } 復制代碼 調用DeepClone方法 復制代碼 Student stu1 = new Student() { IDCode = "lily", Age = 24, Grent = new Grent() { Name="五年三班"} }; Student stu2 = stu1.DeepClone() as Student; if (stu2 == null) { Console.WriteLine("轉換失敗"); Console.ReadKey(); return; } Console.WriteLine(stu2.IDCode); Console.WriteLine(stu2.Age); Console.WriteLine(stu2.Grent.ToString()); Console.WriteLine("重新為stu1賦值"); stu1.IDCode = "Anagle"; stu1.Age += 10; stu1.Grent.Name = "六年二班"; Console.WriteLine(stu2.IDCode); Console.WriteLine(stu2.Age); Console.WriteLine(stu2.Grent.ToString()); Console.ReadKey(); 復制代碼 輸出結果為: lily 24 五年三班 重新為stu1賦值 lily 24 五年三班