1、淺拷貝與深拷貝的定義
什么是拷貝?拷貝即為常說的復制或者克隆一個對象,並且通過拷貝這些源對象創建新的對象。其中拷貝分為淺拷貝和深拷貝。對於拷貝出來的對象,在使用上有很大的差異,特別是在引用類型上。
淺拷貝:將對象中的所有字段復制到新的對象中。其中,值類型字段被復制到新對象中后,在新對象中的修改不會影響到原先對象的值。而新對象的引用類型則是原先對象引用類型的引用,不是引用自己對象本身。注:在新對象中修改引用類型的值會影響到原先對象;理論上String也是引用類型,但是由於由於該類型比較特殊,Object.MemberwiseClone()方法依舊為其新對象開辟了新的內存空間存儲String的值,在淺拷貝中把String類型當作'值類型'即可。
深拷貝:同樣也是拷貝,但是與淺拷貝不同的是,深拷貝會對引用類型重新在創新一次(包括值類型),在新對象做的任何修改都不會影響到源對象本身。
2、實現淺拷貝與深拷貝
在 .Net平台開發中,要實現拷貝,微軟官方建議繼承ICloneable接口,該接口位於System命名空間下,該接口只實現一個Clone方法,我們可以根據具體項目需求在該方法內實現淺拷貝或者深拷貝。先實現一個淺拷貝,具體代碼如下:
//Equal探索 static void Main() { //創建源對象 Teacher Source = new Teacher("Fode",18,DateTime.Now,22); Source.Print("源對象"); //淺拷貝對象 Teacher Target = Source.Clone() as Teacher; /* 理論上String也是引用類型,但是由於由於該類型比較特殊, Object.MemberwiseClone()方法依舊為其新對象開辟了新的內存空間存儲String的值, 在淺拷貝中把String類型當作'值類型'即可 */ Target.Name = "JJ"; Target.Student.Count = 11; Console.WriteLine("新對象的引用類型的值發生變化"); Target.Print("新對象"); Source.Print("源對象"); Console.ReadKey(); } class Teacher : ICloneable { public Teacher(String name, Int32 age, DateTime birthday, Int32 count) { this._name = name; this._age = age; this._birthday = birthday; this.Student = new Student() { Count = count }; } private Int32 _age; public Int32 Age { get { return _age; } set { _age = value; } } private String _name; public String Name { get { return _name; } set { _name = value; } } private DateTime _birthday; public DateTime Birthday { get { return _birthday; } set { _birthday = value; } } public Student Student { get; set; } public void Print(String title) { Console.WriteLine(title); Console.WriteLine($"基本信息:姓名:{this.Name},年齡:{this.Age},生日:{this.Birthday.ToString("D")}"); Console.WriteLine($"引用類型的值{Student.ToString()}"); Console.WriteLine(); } //實現淺拷貝 public Object Clone() { return this.MemberwiseClone(); } } class Student { public Int32 Count { get; set; } public override string ToString() { return Count.ToString(); } }
其運行結果如下:
可以發現當新對象的引用類型發生改變后,其源對象的引用類型也發生改變(String類型除外),他們共同引用的是Student這個引用類型對象(即使發生變化的是其里面的值類型),而新對象的值類型改變並不會到源類型的值類型。
而對於要實現深拷貝則有很多中方法了,比如在拷貝方法里面 直接一個個屬性字段賦值,但是一旦為源對象新增屬性或者字段的時候,容易忘了修改拷貝方法中的值,最好使用序列化的方法進行深拷貝。深拷貝簡單代碼如下,與淺拷貝同樣的案例,只是重寫了Clone()方法,並在類加了[Serializable]序列化特性標簽:
[Serializable] class Teacher : ICloneable { public Teacher(String name, Int32 age, DateTime birthday, Int32 count) { this._name = name; this._age = age; this._birthday = birthday; this.Student = new Student() { Count = count }; } private Int32 _age; public Int32 Age { get { return _age; } set { _age = value; } } private String _name; public String Name { get { return _name; } set { _name = value; } } private DateTime _birthday; public DateTime Birthday { get { return _birthday; } set { _birthday = value; } } public Student Student { get; set; } public void Print(String title) { Console.WriteLine(title); Console.WriteLine($"基本信息:姓名:{this.Name},年齡:{this.Age},生日:{this.Birthday.ToString("D")}"); Console.WriteLine($"引用類型的值{Student.ToString()}"); Console.WriteLine(); } //實現深拷貝拷貝 public Object Clone() { System.IO.Stream stream = new MemoryStream(); try { System.Runtime.Serialization.IFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); formatter.Serialize(stream, this); stream.Seek(0, SeekOrigin.Begin); return formatter.Deserialize(stream); } finally { stream.Close(); stream.Dispose(); } } } [Serializable] class Student { public Int32 Count { get; set; } public override string ToString() { return Count.ToString(); } } static void Main() { //創建源對象 Teacher Source = new Teacher("Fode", 18, DateTime.Now, 22); Source.Print("源對象"); //深拷貝對象 Teacher Target = Source.Clone() as Teacher; Target.Name = "JJ"; Target.Student.Count = 11; Console.WriteLine("新對象的引用類型的值發生變化"); Target.Print("新對象"); Source.Print("源對象"); Console.ReadKey(); }
其結果如下:
可以發現,此時拷貝后的Target對象與源對象沒有任何關系。修改源對象的引用類型並不會影響對應新對象的值。最后在把代碼優化一下,在一個類中同時實現深拷貝和淺拷貝:
//實現淺拷貝 public Object Clone() { return this.MemberwiseClone(); } /// <summary> /// 獲得淺拷貝對象 /// </summary> /// <returns></returns> public Teacher ShallowClone() { return this.Clone() as Teacher; } /// <summary> /// 獲得深拷貝對象 /// </summary> /// <returns></returns> public Teacher DeepClone() { System.IO.Stream stream = new MemoryStream(); try { System.Runtime.Serialization.IFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); formatter.Serialize(stream, this); stream.Seek(0, SeekOrigin.Begin); return formatter.Deserialize(stream) as Teacher; } finally { stream.Close(); stream.Dispose(); } }
注:ICloneable接口適用於
.NET Core
.NET Framework
.NET Standard
Xamarin.Android
Xamarin.iOS
Xamarin.Mac
Teacher Source = new Teacher("Fode", 18, DateTime.Now, 22); Teacher Target = Source; Source.Print("源對象"); Console.WriteLine("源對象的值類型發生改變"); Source.Name = "JJ"; Source.Age = 22; Source.Print("源對象"); Target.Print("新對象"); Console.WriteLine("新對象的值類型發生改變"); Target.Name = "范冰冰"; Target.Age = 18; Source.Print("源對象"); Target.Print("新對象"); Console.ReadKey();
輸出結果如下: