Effective C# 避免ICloneable介面

來源:互聯網
上載者:User

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

任何只包含內建類型成員的實值型別都不需要支援 ICloneable介面;一個簡單的指派陳述式對struct的值所做的複製要比Clone()來得高效得多。Clone()必須對傳回值進行裝箱,才能轉換為一個System.Object引用。調用者則必須進行強制轉型才能擷取真正的值。實值型別預設的複製支援對我們來說已經足夠了。我們沒有必要再編寫 Clone()函數來重複這項工作。

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

 

public struct ErrorMessage

{

private int errCode;

private int details;

private string msg;

// 忽略細節。

}

 

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

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

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

 

 

class BaseType : ICloneable

{

private string _label = "class name";

private int [] _values = new int [ 10 ];

public object Clone()

{

    BaseType rVal = new BaseType( );

    rVal._label = _label;

    for( int i = 0; i < _values.Length; i++ )

      rVal._values[ i ] = _values[ i ];

    return rVal;

}

}

class Derived : BaseType

{

private double [] _dValues = new double[ 10 ];

static void Main( string[] args )

{

    Derived d = new Derived();

    Derived d2 = d.Clone() as Derived;

    if ( d2 == null )

      Console.WriteLine( "null" );

}

}

 

如果運行上面的程式,我們將發現d2的值為 null。Derived類從BaseType類中繼承了ICloneable.Clone()方法,但是繼承來的實現對Derived類型來講卻是不正確的,因為它僅僅複製了基類。BaseType.Clone()建立了一個BaseType對象,而非一個Derived對象。這就是測試程式中d2返回 null的原因——它不是一個Derived對象。但是,即使我們能夠克服這個問題,BaseType.Clone()也不能對Derived中定義的 _dValues數組進行正確的複製。當我們的類型實現了ICloneable介面,就會強制要求其所有衍生類別也實現ICloneable介面。實際上,這時候我們應該提供一個掛鈎函數(hook function)來允許所有衍生類別使用我們的實現。為了支援複製,衍生類別只可以添加那些支援ICloneable介面的實值型別或參考型別成員變數。這對所有的衍生類別來說是一個非常嚴格的限制。因此我們說,為基類添加ICloneable介面支援通常會為其衍生類別帶來一些負擔,所以我們應該避免在非密封(nonsealed)類中實現ICloneable介面。

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

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

 

class BaseType

{

private string _label;

private int [] _values;

protected BaseType( )

{

    _label = "class name";

    _values = new int [ 10 ];

}

// 供衍生類別用來做clone。

protected BaseType( BaseType right )

{

    _label = right._label;

    _values = right._values.Clone( ) as int[ ] ;

}

}

sealed class Derived : BaseType, ICloneable

{

private double [] _dValues = new double[ 10 ];

public Derived ( )

{

    _dValues = new double [ 10 ];

}


// 使用基類的“複製構造器”構造一個副本。

private Derived ( Derived right ) :

    base ( right )

{

    _dValues = right._dValues.Clone( )

      as double[ ];

}

static void Main( string[] args )

{

    Derived d = new Derived();

    Derived d2 = d.Clone() as Derived;

    if ( d2 == null )

      Console.WriteLine( "null" );

}

public object Clone()

{

    Derived rVal = new Derived( this );

    return rVal;

}

}

 

 

在上面的代碼中,我們的基類BaseType沒有實現ICloneable介面,但它提供了一個受保護的複製構造器,以使衍生類別可以複製其內的成員。如果有必要,“葉子類”——即那些密封類——可以實現 ICloneable介面。我們的基類沒有強制要求所有的衍生類別實現ICloneable介面,但它為所有希望實現ICloneable介面的衍生類別提供了必要的方法支援。

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

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.