天天看點

淺析C#深拷貝與淺拷貝

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

1.深拷貝與淺拷貝

         拷貝即是通常所說的複制(copy)或克隆(clone),對象的拷貝也就是從現有對象複制一個“一模一樣”的新對象出來。雖然都是複制對象,但是不同的 複制方法,複制出來的新對象卻并非完全一模一樣,對象内部存在着一些差異。通常的拷貝方法有兩種,即深拷貝和淺拷貝,那二者之間有何差別呢?msdn裡對 iclone接口的clone方法有這樣的說明:在深層副本中,所有的對象都是重複的;而在淺表副本中,隻有頂級對象是重複的,并且頂級以下的對象包含引 用。可以看出,深拷貝和淺拷貝之間的差別在于是否複制了子對象。這如何了解呢?下面我通過帶有子對象的代碼來驗證二者的差別。

        首先定義兩個類型:student和classroom,其中student類型裡包含classroom,并使這兩個類型都分别實作自定義的深拷貝接口(ideepcopy)和淺拷貝接口(ishallowcopy)。

類圖如下:

淺析C#深拷貝與淺拷貝

定義代碼如下:

測試代碼:

運作結果:

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

-----------------------------

        從以上結果可以看出,深拷貝時兩個對象是完全“分離”的,改變其中一個,不會影響到另一個對象;

        淺拷貝時兩個對象并未完全“分離”,改變頂級對象的内容,不會對另一個對象産生影響,但改變子對象的内容,則兩個對象同時被改變。

        這種差異的産生,即是取決于拷貝子對象時複制記憶體還是複制指針。

       深拷貝為子對象重新配置設定了一段記憶體空間,并複制其中的内容;淺拷貝僅僅将指針指向原來的子對象。

示意圖如下:

淺析C#深拷貝與淺拷貝
淺析C#深拷貝與淺拷貝

2.淺拷貝與指派操作

        大多數面向對象語言中的指派操作都是傳遞引用,即改變對象的指針位址,而并沒有複制記憶體,也沒有做任何複制操作。

       由此可知,淺拷貝與指派操作的差別是頂級對象的複制與否。當然,也有一些例外情況,比如類型定義中重載指派操作符(assignment operator),或者某些類型約定按值傳遞,就像c#中的結構體和枚舉類型。

指派操作示意圖如下:

淺析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的反射機制,以前寫了一個通用的序列化方法,現在可以拿過來,先序列化,然後再反序列化回來,也即是一個深拷貝,示例代碼如下:

深拷貝示例代碼: