ICloneable深複製與淺複製

2021-04-16 22:16:15 字數 2924 閱讀 9095

c# 深複製與淺複製

2008/01/18 11:37

icloneable聽起來是個好主意:可以為那些支援複製的型別實現icloneable介面。如果不想支援複製,那就不要實現它。但是我們的型別並非活在真空中。讓乙個型別支援icloneable介面會影響它的派生類。一旦型別支援icloneable介面,那麼它所有的派生類也都必須支援它。而且,其所有成員型別也都要支援icloneable介面,或者有其他建立複製的機制。最後,當我們設計的型別包含交織成網狀的物件時,支援深複製將變得很困難。icloneable介面在其官方的定義裡很巧妙地繞過了這個問題,其定義如下:icloneable介面或者支援深複製(deep copy),或者支援淺複製(shallow copy)。淺複製指的是新物件包含所有成員變數的副本,如果成員變數為引用型別,那麼新物件將和原物件引用同樣的物件。深複製指的也是新物件包含所有成員變數的副本,但是所有引用型別的成員變數將被遞迴地轉殖。對於c#的內建型別,例如整數,深複製和淺複製產生的是同樣的結果。那麼我們的型別應該支援哪乙個?這要根據具體型別而定。但是在同乙個物件中混合淺複製和深複製會導致許多不一致的問題。當涉足icloneable介面時,這樣的問題很難逃脫。大多數情況下,避免icloneable介面反倒會獲得乙個比較簡單的類——對類的客戶來講比較容易使用,對建立者來講也比較容易實現。

任何只包含內建型別成員的值型別都不需要支援icloneable介面;乙個簡單的賦值語句對struct的值所做的複製要比clone()來得高效得多。clone()必須對返回值進行裝箱,才能轉換為乙個system.object引用。呼叫者則必須進行強制轉型才能獲取真正的值。值型別預設的複製支援對我們來說已經足夠了。我們沒有必要再編寫clone()函式來重複這項工作。

如果值型別中包含引用型別呢?最明顯的例子是包含字串:

public struct errormessage

字串是乙個特殊的例子,因為string是乙個具有常量性的類。如果我們對errormessage物件進行賦值,兩個errormessage物件都將引用同乙個字串。但這並不會導致任何問題,而這放到乙個普通的引用型別就會出現問題。通過任何乙個物件更改msg變數,都會建立乙個新的string物件(參見條款7)。

更一般的情況——建立乙個包含任意引用型別變數的struct——就比較複雜了。不過這種情況相當少見。c#語言為struct提供的內建賦值操作建立的是乙個淺複製——即兩個struct引用的是同乙個引用型別物件。要建立乙個深複製,我們需要轉殖其內包含的引用型別,而且需要確知其clone()方法支援深複製。無論哪種情況,我們都沒有必要為值型別新增icloneable介面支援——賦值操作符可以建立任何值型別的新副本。

綜上所述,對值型別來講,提供icloneable介面的理由不夠充分。下面我們來看引用型別。引用型別要通過支援icloneable介面來表明自身支援淺複製或者深複製。但是在為乙個類新增icloneable介面支援時,我們要審慎行事,因為那樣做會強制要求該類的所有派生類也都必須支援icloneable介面。考慮下面兩個類:

class basetype : icloneable

}class derived : basetype

}如果執行上面的程式,我們將發現d2的值為null。derived類從basetype類中繼承了icloneable.clone()方法,但是繼承來的實現對derived型別來講卻是不正確的,因為它僅僅轉殖了基類。basetype.clone()建立了乙個basetype物件,而非乙個derived物件。這就是測試程式中d2返回null的原因——它不是乙個derived物件。但是,即使我們能夠克服這個問題,basetype.clone()也不能對derived中定義的_dvalues陣列進行正確的複製。當我們的型別實現了icloneable介面,就會強制要求其所有派生類也實現icloneable介面。實際上,這時候我們應該提供乙個掛鉤函式(hook function)來允許所有派生類使用我們的實現(參見條款21)。為了支援轉殖,派生類只可以新增那些支援icloneable介面的值型別或引用型別成員變數。這對所有的派生類來說是乙個非常嚴格的限制。因此我們說,為基類新增icloneable介面支援通常會為其派生類帶來一些負擔,所以我們應該避免在非密封(nonsealed)類中實現icloneable介面。

如果整個類層次必須實現icloneable介面,我們可以建立乙個抽象的clone()方法,並強制要求所有的派生類實現它。

這時候,我們需要定義一種方式,使派生類可以建立基類成員的副本。這可以通過定義乙個protected的複製構造器來實現:

class basetype

// 供派生類用來做clone。

protected basetype( basetype right )

}sealed class derived : basetype, icloneable

// 使用基類的「複製構造器」構造乙個副本。

private derived ( derived right ) :

base ( right )

static void main( string args )

public object clone()

}在上面的**中,我們的基類basetype沒有實現icloneable介面,但它提供了乙個受保護的複製構造器,以使派生類可以複製其內的成員。如果有必要,「葉子類」——即那些密封類——可以實現icloneable介面。我們的基類沒有強制要求所有的派生類實現icloneable介面,但它為所有希望實現icloneable介面的派生類提供了必要的方法支援。

icloneable介面有其價值所在,但那都是特例,而非普遍的規則。對於值型別來講,我們永遠都不需要支援icloneable介面,使用預設的賦值操作就可以了。我們應該為那些確實需要複製操作的「葉子類」提供icloneable介面支援。對於那些子類可能需要支援icloneable介面的基類,我們應該為其建立乙個受保護的複製構造器。除此之外,我們應該避免支援icloneable介面

ICloneable介面的淺拷貝與深拷貝

icloneable介面支援轉殖,即用與現有例項相同的值建立類的新例項。icloneable 介面包含乙個成員 clone,它用於支援除 memberwiseclone 建立當前 object的淺表副本 所提供的轉殖之外的轉殖。msdn上給出memberwiseclone方法的解釋是memberwi...

Python深複製淺複製or深拷貝淺拷貝

簡單點說 1.copy.copy 淺拷貝 只拷貝父物件,不會拷貝物件的內部的子物件。2.copy.deepcopy 深拷貝 拷貝物件及其子物件 用乙個簡單的例子說明如下 import copy a 1,2,3,4,a b c b a c copy.copy a d copy.deepcopy a 很...

轉 Python深複製淺複製or深拷貝淺拷貝

copy.copy 淺拷貝 只拷貝父物件,不會拷貝物件的內部的子物件。copy.deepcopy 深拷貝 拷貝物件及其子物件 用乙個簡單的例子說明如下 import copy a 1,2,3,4,a b c b a c copy.copy a d copy.deepcopy a 很容易理解 a是乙個...