1. 程式人生 > >瞭解C# Tips淺拷貝和深拷貝(轉)

瞭解C# Tips淺拷貝和深拷貝(轉)

引言   C#中有兩種型別變數,一種 是值型別變數,一種是引用型別變數,對於值型別變數,深拷貝和前拷貝都是通過賦值操作符號(=)實現,其效果一致,將物件中的值型別的欄位拷貝到新的物件中.這個很容易理解。 本文重點討論引用型別變數的拷貝機制和實現。   C#中引用型別物件的copy操作有兩種:   淺拷貝(影子克隆/shallow copy):只複製物件的值型別欄位,物件的引用型別,仍屬於原來的引用. 深拷貝(深度克隆):不僅複製物件的值型別欄位,同時也複製原物件中的物件.就是說完全是新物件產生的.   淺拷貝和深拷貝之間的區別:淺拷貝是指將物件中的數值型別的欄位拷貝到新的物件中,而物件中的引用型欄位則指複製它的一個引用到目標物件。   注意:string型別有點特殊,對於淺拷貝,類值型別物件進行處理。   淺拷貝的實現   使用Object類MemberwiseClone實現   MemberwiseClone:建立當前 Object 的淺表副本。   MemberwiseClone 方法建立一個淺表副本,方法是建立一個新物件,然後將當前物件的非靜態欄位複製到該新物件。如果欄位是值型別的,則對該欄位執行逐位複製。如果欄位是引用型別,則複製引用但不復制引用的物件;因此,原始物件及其複本引用同一物件。   程式碼實現如下: public class Person   {   public int Age { get; set; }   public string Address { get; set; }   public Name Name { get; set; }   public object Clone()   {   return this.MemberwiseClone();   }   }   public class Name   {   public Name(string frisName,string lastName)   {   FristName = frisName;   LastName = lastName;   }   public string FristName { get; set; }   public string LastName { get; set; }   } 賦值操作(=)VS使用Object類MemberwiseClone實現   對於引用型別的變數,我們有種誤解,認為賦值操作就是淺拷貝一種,其實不然,兩者有區別。   淺拷貝(shallow copy)對於引用型別物件中的值型別欄位進行了逐位複製。賦值運算子只是把源物件的引用賦值給目的物件,兩者引用同一個物件。 淺拷貝後的物件的值型別欄位更改不會反映到源物件,而賦值運算後的物件的值型別欄位更改會反映到源物件 程式碼實現如下:    public class Person   {   public int Age { get; set; }   public string Address { get; set; }   public Name Name { get; set; }   }   public class Name   {   public Name(string frisName,string lastName)   {   FristName = frisName;   LastName = lastName;   }   public string FristName { get; set; }   public string LastName { get; set; }   }   深拷貝實現   相對於淺拷貝,是指依照源物件為原型,建立一個新物件,將當前物件的所有欄位進行執行逐位複製並支援遞迴,不管是是值型別還是引用型別,不管是靜態欄位還是非靜態欄位。   在C#中,我們們有三種方法實現深拷貝   實現ICloneable介面,自定義拷貝功能。   ICloneable 介面,支援克隆,即用與現有例項相同的值建立類的新例項。   ICloneable 介面包含一個成員 Clone,它用於支援除 MemberwiseClone 所提供的克隆之外的克隆。Clone 既可作為深層副本實現,也可作為淺表副本實現。在深層副本中,所有的物件都是重複的;而在淺表副本中,只有頂級物件是重複的,並且頂級以下的物件包含引用。 結果克隆必須與原始例項具有相同的型別或是原始例項的相容型別。   程式碼實現如下:  Code   public class Person:ICloneable   {   public int Age { get; set; }   public string Address { get; set; }   public Name Name { get; set; }   public object Clone()   {   Person tem = new Person();   tem.Address = this.Address;   tem.Age = this.Age;   tem.Name = new Name(this.Name.FristName, this.Name.LastName);   return tem;   }   }   public class Name   {   public Name(string frisName, string lastName)   {   FristName = frisName;   LastName = lastName;   }   public string FristName { get; set; }   public string LastName { get; set; }   }   大家可以看到,Person類繼承了介面ICloneable並手動實現了其Clone方法,這是個簡單的類,試想一下,如果你的類有成千上萬個引用型別成員(當然太誇張,幾十個還是有的),這是不是份很恐怖的勞力活?   序列化/反序列化類實現   不知道你有沒有注意到DataSet物件,對於他提供的兩個方法:   DataSet.Clone 方法,複製 DataSet 的結構,包括所有 DataTable 架構、關係和約束。不要複製任何資料。   新 DataSet,其架構與當前 DataSet 的架構相同,但是不包含任何資料。注意 如果已建立這些類的子類,則複本也將屬於相同的子類。   DataSet.Copy 方法複製該 DataSet 的結構和資料.   新的 DataSet,具有與該 DataSet 相同的結構(表架構、關係和約束)和資料。注意如果已建立這些類的子類,則副本也將屬於相同的子類。   好像既不是淺拷貝,又不是深拷貝,是不是很失望?但是兩個結合起來不是我們要的深拷貝嗎?看看DataSet的實現,注意序列化介面:ISerializable   序列化是將物件或物件圖形轉換為線性位元組序列,以儲存或傳輸到另一個位置的過程。   反序列化是接受儲存的資訊並利用它重新建立物件的過程。   通過 ISerializable 介面,類可以執行其自己的序列化行為。   轉換為線性位元組序列後並利用其重新建立物件的過程是不是和我們的深拷貝的語意“逐位複製”很相像?   程式碼實現如下: [Serializable]   public class Person : ICloneable   {   public int Age { get; set; }   public string Address { get; set; }   public Name Name { get; set; }   public object Clone()   {   using (MemoryStream ms = new MemoryStream(1000))   {   object CloneObject;   BinaryFormatter bf = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone));   bf.Serialize(ms, this);   ms.Seek(0, SeekOrigin.Begin);   // 反序列化至另一個物件(即建立了一個原物件的深表副本)   CloneObject = bf.Deserialize(ms);   // 關閉流   ms.Close();   return CloneObject;   }   }   }   [Serializable]   public class Name   {   public Name(string frisName, string lastName)   {   FristName = frisName;   LastName = lastName;   }   public string FristName { get; set; }   public string LastName { get; set; }   }   }   注意:通過序列化和反序列化實現深拷貝,其和其欄位型別必須標記為可序列化型別,既新增特性(Attribute)[Serializable]。   通過反射實現   通過序列化/反序列化方式我們能比較流暢的實現深拷貝,但是涉及到IO操作,託管的的環境中,IO操作比較消耗資源。 能不能有更優雅的解決方案。CreateInstance,對,利用反射特性。這個方法大家可以參考這篇部落格:http://rubenhak.com/?p=70 文章反射類的Attribute,利用Activator.CreateInstance New一個類出來(有點像DataSet.Clone先獲得架構),然後利用PropertyInfo的SetValue和GetValue方法,遍歷的方式進行值填充。   程式碼實現如下:  public class Person   {   private List _friends = new List();   public string Firstname { get; set; }   public string Lastname { get; set; }   [Cloneable(CloneableState.Exclude)]   [Cloneable(CloneableState.Include, "Friends")]   public List Friends { get { return _friends; } }   [Cloneable(CloneableState.Exclude)]   public PersonManager Manager { get; set; }   }