MemberwiseClone 方法創建一個淺表副本,具體來說就是創建一個新對象,然后將當前對象的非靜態字段復制到該新對象。
如果字段是值類型的,則對該字段執行逐位復制。如果字段是引用類型,則復制引用但不復制引用的對象;因此,原始對象及其復本引用同一對象。
為了實現深度復制,我們就必須遍歷有相互引用的對象構成的圖,並需要處理其中的循環引用結構。這無疑是十分復雜的。幸好借助.Net的序列化和反序 列化機制,可以十分簡單的深度Clone一個對象。原理很簡單,首先將對象序列化到內存流中,此時對象和對象引用的所用對象的狀態都被保存到內存 中。.Net的序列化機制會自動處理循環引用的情況。然后將內存流中的狀態信息反序列化到一個新的對象中。這樣一個對象的深度復制就完成了。在原型設計模 式中CLONE技術非常關鍵。
下面的代碼就是演示這個問題:
using System; using System.IO; using System.Runtime.Serialization.Formatters.Binary; namespace CloneDemo { [Serializable] class DemoClass { public int i = 0; public int[] iArr = { 1, 2, 3 }; public DemoClass Clone1() //淺CLONE { return this.MemberwiseClone() as DemoClass; } public DemoClass Clone2() //深clone { MemoryStream stream = new MemoryStream(); BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(stream, this); stream.Position = 0; return formatter.Deserialize(stream) as DemoClass; } } class Program { static void Main(string[] args) { DemoClass a = new DemoClass(); a.i = 10; a.iArr = new int[] { 8, 9, 10 }; DemoClass b = a.Clone1(); DemoClass c = a.Clone2(); // 更改 a 對象的iArr[0], 導致 b 對象的iArr[0] 也發生了變化 而 c不會變化 a.iArr[0] = 88; Console.WriteLine("MemberwiseClone"); Console.WriteLine(b.i); foreach (var item in b.iArr) { Console.WriteLine(item); } Console.WriteLine("Clone2"); Console.WriteLine(c.i); foreach (var item in c.iArr) { Console.WriteLine(item); } Console.ReadLine(); } } }
另外一個例子是針對數組,C#中的數組是引用型的變量,我們通過數組來進行演示:
淺拷貝
using System; class ShallowCopy : ICloneable { public int[] v = {1,2,3}; public Object Clone() { return this.MemberwiseClone(); } public void Display() { foreach(int i in v) Console.Write( i + ", "); Console.WriteLine(); } } class Client { public static void Main() { ShallowCopy sc1 = new ShallowCopy(); ShallowCopy sc2 = (ShallowCopy)sc1.Clone(); sc1.v[0] = 9; sc1.Display(); sc2.Display(); } }
ShallowCopy對象實現了一個淺拷貝,因此當對sc1進行克隆時,其字段v並沒有克隆,這導致sc1與sc2的字段v都指向了同一個v,因此,當修改了sc1的v[0]后,sc2的v[0]也發生了變化。
深拷貝:
using System; class DeepCopy : ICloneable { public int[] v = {1,2,3}; // 默認構造函數 public DeepCopy() { } // 供Clone方法調用的私有構造函數 private DeepCopy(int[] v) { this.v = (int[])v.Clone(); } public Object Clone() { // 構造一個新的DeepCopy對象,構造參數為 // 原有對象中使用的 v return new DeepCopy(this.v); } public void Display() { foreach(int i in v) Console.Write( i + ", "); Console.WriteLine(); } } class Client { public static void Main() { DeepCopy dc1 = new DeepCopy(); DeepCopy dc2 = (DeepCopy)dc1.Clone(); dc1.v[0] = 9; dc1.Display(); dc2.Display(); } }
這次在克隆的時候,不但克隆對象本身,連里面的數組字段一並克隆。因此,最終打印出來的dc1與dc2不同。
當然我們也可以建個深拷貝的幫助類:
public static class ObjectCopier { /// <summary> /// Perform a deep Copy of the object. /// </summary> /// <typeparam name="T">The type of object being copied.</typeparam> /// <param name="source">The object instance to copy.</param> /// <returns>The copied object.</returns> public static T Clone<T>(T source) { if (!typeof(T).IsSerializable) { throw new ArgumentException("The type must be serializable.", "source"); } // Don't serialize a null object, simply return the default for that object if (Object.ReferenceEquals(source, null)) { return default(T); } IFormatter formatter = new BinaryFormatter(); Stream stream = new MemoryStream(); using (stream) { formatter.Serialize(stream, source); stream.Seek(0, SeekOrigin.Begin); return (T)formatter.Deserialize(stream); } } }