淺析C#深拷貝與淺拷貝 (轉)


from: http://blog.csdn.net/lai123wei/article/details/7217365

 

 

1.深拷貝與淺拷貝
  拷貝即是通常所說的復制(Copy)或克隆(Clone),對象的拷貝也就是從現有對象復制一個“一模一樣”的新對象出來。雖然都是復制對象,但是不同的 復制方法,復制出來的新對象卻並非完全一模一樣,對象內部存在着一些差異。通常的拷貝方法有兩種,即深拷貝和淺拷貝,那二者之間有何區別呢?MSDN里對 IClone接口的Clone方法有這樣的說明:在深層副本中,所有的對象都是重復的;而在淺表副本中,只有頂級對象是重復的,並且頂級以下的對象包含引 用。可以看出,深拷貝和淺拷貝之間的 區別在於是否復制了子對象。這如何理解呢?下面我通過帶有子對象的代碼來驗證二者的區別。
首先定義兩個類型:Student和ClassRoom,其中Student類型里包含ClassRoom,並使這兩個類型都分別實現自定義的深拷貝接口(IDeepCopy)和淺拷貝接口(IShallowCopy)。
類圖如下:

定義代碼如下:

 

View Code
///   <summary>
    
///  深拷貝接口
    
///   </summary>
     interface IDeepCopy
    {
         object DeepCopy();
    }

     ///   <summary>
    
///  淺拷貝接口
    
///   </summary>
     interface IShallowCopy
    {
         object ShallowCopy();
    }

     ///   <summary>
    
///  教室信息
    
///   </summary>
     class ClassRoom : IDeepCopy, IShallowCopy
    {
         public  int RoomID =  1;
         public  string RoomName =  " Room1 ";

         public  override  string ToString()
        {
             return  " RoomID= " + RoomID +  " \tRoomName= " + RoomName;
        }
         public  object DeepCopy()
        {
            ClassRoom r =  new ClassRoom();
            r.RoomID =  this.RoomID;
            r.RoomName =  this.RoomName;
             return r;
        }
         public  object ShallowCopy()
        {
             // 直接使用內置的淺拷貝方法返回
             return  this.MemberwiseClone();
        }
    }

     class Student : IDeepCopy, IShallowCopy
    {
         // 為了簡化,使用public 字段
         public  string Name;
         public  int Age;
         // 自定義類型,假設每個Student只擁有一個ClassRoom
         public ClassRoom Room =  new ClassRoom();

         public Student()
        {
        }
         public Student( string name,  int age)
        {
             this.Name = name;
             this.Age = age;
        }
         public  object DeepCopy()
        {
            Student s =  new Student();
            s.Name =  this.Name;
            s.Age =  this.Age;
            s.Room = (ClassRoom) this.Room.DeepCopy();
             return s;
        }
         public  object ShallowCopy()
        {
             return  this.MemberwiseClone();
        }

         public  override  string ToString()
        {
             return  " Name: " + Name +  " \tAge: " + Age +  " \t " + Room.ToString();
        }

    }
pasting
pasting


測試代碼:

 

View Code
Student s1 =  new Student( " Vivi "28); 
Console.WriteLine( " s1=[ " + s1 +  " ] "); 
Student s2 = (Student)s1.ShallowCopy(); 
// Student s2 = (Student)s1.DeepCopy(); 
Console.WriteLine( " s2=[ " + s2 +  " ] ");  // 此處s2和s1內容相同 
Console.WriteLine( " ----------------------------- "); 
// 修改s2的內容 
s2.Name =  " tianyue "
s2.Age =  25
s2.Room.RoomID =  2
s2.Room.RoomName =  " Room2 "
Console.WriteLine( " s1=[ " + s1 +  " ] "); 
Console.WriteLine( " s2=[ " + s2 +  " ] "); 
// 再次打印兩個對象以比較 
Console.ReadLine();

 

運行結果:

a.ShallowCopy
s1=[Name:Vivi   Age:28  RoomID=1        RoomName=Room1]
s2=[Name:Vivi   Age:28  RoomID=1        RoomName=Room1]
-------------------------------------------------------------
s1=[Name:Vivi   Age:28  RoomID=2        RoomName=Room2]
s2=[Name:tianyue        Age:25  RoomID=2        RoomName=Room2]

b.DeepCopy
s1=[Name:Vivi   Age:28  RoomID=1        RoomName=Room1]
s2=[Name:Vivi   Age:28  RoomID=1        RoomName=Room1]
-----------------------------
s1=[Name:Vivi   Age:28  RoomID=1        RoomName=Room1]
s2=[Name:tianyue        Age:25  RoomID=2        RoomName=Room2]
從以上結果可以看出,深拷貝時兩個對象是完全“分離”的,改變其中一個,不會影響到另一個對象;
淺拷貝時兩個對象並未完全“分離”,改變頂級對象的內容,不會對另一個對象產生影響,但改變子對象的內容,則兩個對象同時被改變。
這種差異的產生,即是取決於拷貝子對象時復制內存還是復制指針。深拷貝為子對象重新分配了一段內存空間,並復制其中的內容;
淺拷貝僅僅將指針指向原來的子對象。
示意圖如下:
2.淺拷貝與賦值操作
大多數面向對象語言中的賦值操作都是傳遞引用,即改變對象的指針地址,而並沒有復制內存,也沒有做任何復制操作。
由此可知,淺拷貝與賦值操作的區別是 頂級對象的復制與否。當然,也有一些例外情況,比如類型定義中重載賦值操作符(assignment operator),
或者某些類型約定按值傳遞,就像C#中的結構體和枚舉類型。
賦值操作示意圖如下:
3.C++拷貝構造函數
與其它面向對象語言不同,C++允許用戶選擇自定義對象的傳遞方式:值傳遞和引用傳遞。在值傳遞時就要使用對象拷貝,比如說按值傳遞參數,編譯 器需要拷貝一個對象以避免原對象在函數體內被破壞。為此,C++提供了拷貝構造函數用來實現這種拷貝行為,拷貝構造函數是一種特殊的構造函數,用來完成一 些基於同一類的其它對象的構造和初始化。它唯一的參數是引用類型的,而且不可改變,通常的定義為X(const X&)。在拷貝構造函數里,用戶可以定義對象的拷貝行為是深拷貝還是淺拷貝,如果用戶沒有實現自己的拷貝構造函數,那么編譯器會提供一個默認實 現,該實現使用的是按位拷貝(bitwise copy),也即本文所說的淺拷貝。構造函數何時被調用呢?通常以下三種情況需要拷貝對象,此時拷貝構造函數將會被調用。
1.一個對象以值傳遞的方式傳入函數體
2.一個對象以值傳遞的方式從函數返回
3.一個對象需要通過另外一個對象進行初始化
4.C# MemberwiseClone與ICloneable接口
和C++里的拷貝構造函數一樣,C#也為每個對象提供了淺拷貝的默認實現,不過C#里沒有拷貝構造函數,而是通過頂級類型Object里的 MemberwiseClone方法。MemberwiseClone 方法創建一個淺表副本,方法是創建一個新對象,然后將當前對象的非靜態字段復制到該新對象。有沒有默認的深拷貝實現呢?當然是沒有,因為需要所有參與拷貝 的對象定義自己的深拷貝行為。C++里需要用戶實現拷貝構造函數,重寫默認的淺拷貝;C#則不同,C#(確切的說是.NET Framework,而非C#語言)提供了ICloneable 接口,包含一個成員 Clone,它用於支持除 MemberwiseClone 所提供的克隆之外的克隆。C++通過拷貝構造函數無法確定子對象實現的是深拷貝還是淺拷貝,而C#在“強制”實現淺拷貝的基礎上,提供 ICloneable 接口由用戶定義深拷貝行為,通過接口來強制約束所有參與拷貝的對象,個人覺得,這也算是一小點C#對C++的改進。
 
5.深拷貝策略與實現
深拷貝的要點就是確保所有參與拷貝的對象都要提供自己的深拷貝實現,不管是C++拷貝構造函數還是C#的ICloneable 接口,事實上都是一種拷貝的約定。有了事先的約定,才能約束實現上的統一,所以關鍵在於設計。
但偶爾也會在后期才想到要深拷貝,怎么辦?總不能修改所有之前的實現吧。有沒有辦法能夠通過頂級類而不關心內部的子對象直接進行深拷貝呢?能不 能搞個萬能的深拷貝方法,在想用的時候立即用,而不考慮前期的設計。這樣“大包大攬”的方法,難點在於實現時必須自動獲取子對象的信息,分別為子對象實現 深拷貝。C++里比較困難,.NET的反射機制使得實現容易一些。不過這樣的方法雖然通用,實則破壞了封裝,也不符合“每個類對自己負責”的設計原則。
 
基於.NET的反射機制,以前寫了一個通用的序列化方法,現在可以拿過來,先序列化,然后再反序列化回來,也即是一個深拷貝,示例代碼如下:
深拷貝示例代碼:
 

 

View Code
         #region ICloneable Members

         ///   <summary>
        
///  此處的復制為深拷貝,在實現上,為了簡化,采用序列化和反序列化。
        
///   </summary>
        
///   <returns> 深拷貝對象 </returns>
         public  object Clone()
        {
            Student stu =  new Student();
            XmlStorageHelper helper =  new XmlStorageHelper();
             string strXml = helper.ConvertToString( this);
            helper.LoadFromString(stu, strXml);    // 從XML字符串來賦值

             return stu;
        }

         #endregion


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM