天天看點

.NET對象克隆

< DOCTYPE html PUBLIC -WCDTD XHTML StrictEN httpwwwworgTRxhtmlDTDxhtml-strictdtd>

大家一定知道.NET對象是有二大類型的: 值類型和引用類型。 值類型對象的變量表示對象本身,而且具有“copy-on-assignment”的行為。也就是說, 以下的讨論不适用于值類型。

另一方面,引用類型的變量實際上是指向堆上的記憶體。 是以,如果你建立了一個引用類型的變量,并且将一個已存在的對象配置設定給它,實際上是建立了指向堆上的相同記憶體的另外一個對象。本文就是讨論這樣的情況:建立的一個對象的新的拷貝,并且儲存在一個變量中!

為什麼要克隆?

我認為當設定一個對象的狀态要付出昂貴的代價,并且又需要取得該對象的一個拷貝以便改變目前的一些狀态時,克隆就顯得十分必要。下面列舉一個剛好能展現我剛剛所說的情況的例子。 就拿 DataTable 類來說吧。建立一個 DataTable 會包含諸如以下的操作:為取得架構和資料而查詢資料庫、添加限制、設定主鍵等等。那麼,當需要該 DataTable 的一個新的拷貝,哪怕是對架構作極小的改變或添加新的一行記錄等等, 明智的選擇會是克隆已存在的對象再對其進行操作,而不是建立一個新的DataTable, 那樣将需要更多的時間和資源。

克隆也廣泛應用于數組和集合,這些時候往往會多次需要已存在對象的一個拷貝。

克隆的類型

我們基于克隆的程度将克隆分成兩大類:“深層”克隆和“淺表”克隆。“淺表”克隆得到一個新的執行個體,一個與原始對象類型相同、包含值類型字段的拷貝。但是,如果字段是引用類型的, 該引用将被拷貝, 而不是拷貝引用的對象。 是以,原始對象的引用和克隆對象的引用都指向同一個對象。另一方面, 對象的“深層”克隆包含原始對象直接或間接引用的對象的所有拷貝。下面舉例說明。

對象X引用對象A,對象A引用對象M。對象X的“淺表”克隆對象Y,同樣也引用了對象A。相對比的是,對象X的“深層”克隆對象Y,卻直接引用了對象B,并且間接引用對象N,這裡,對象B是對象A的拷貝,對象N是對象M的拷貝。

實作克隆

System.Object提供了受保護的方法 MemberwiseClone,可用來實作“淺表”克隆。由于該方法标記為“受保護”級别,是以,我們隻能在繼承類或該類内部才能通路該方法。

.NET定義了一個IClonable接口,一個需要“深層”克隆而不是“淺表”克隆的類必須實作該接口。我們需要提供一個好的實作方法來達到該接口的Clone方法實作的功能。

有許多方法可以實作“深層”克隆。一個方法是将對象串行化到記憶體流中,然後反串行化到一個新的對象。我們将使用一個二進制(Binary)的 Formatter類或SOAP formatter類來進行深層串行化。做一個深寫成連載長篇而刊登的 formatter 。 這個方法的問題是類和它的成員 (完整的類表) 必須被标記為serializable,否則formatter會發生錯誤。

反射是另外一個能達到相同目的的方法。 Amir Harel寫的一篇好文章吸引了我, 他使用該方法提供一個好的克隆實作。 這篇文章讨論得非常好! 以下是連結:

<a href="http://www.codeproject.com/csharp/cloneimpl_class.asp">http://www.codeproject.com/csharp/cloneimpl_class.asp</a>

上面讨論的任何一個方法,都要求對象的成員類型能支援自我克隆,以確定“深層”克隆能成功進行。也就是說, 對象必須是可串行化的(serializable) ,或者每個獨立的成員必須提供IClonable的實作。 如果不這樣,我們根本不可能對對象進行“深層”克隆!

綜述

克隆是提供給程式員的一個很好的方法。但是, 我們應該知道什麼時候需要提供這樣的功能,而且在某些情況下,嚴格地說,對象不應該提供這一個特性。 以SQLTransaction 類為例, 就不支援克隆。這一個類代表了SQL Server資料庫的一個事務。 克隆該對象沒有任何意義,因為我們可能不能夠了解一個資料庫的一個活動的事務的克隆! 是以,如果你認為克隆對象的狀态會産生應用程式邏輯上的沖突,就不需要支援克隆。

示例代碼:

using System;

using System.Reflection;

using System.Collections;

namespace Amir_Harel.Cloning

{

/// &lt;summary&gt;

/// &lt;b&gt;BaseObject&lt;/b&gt; class is an abstract class for you to derive from. &lt;br&gt;

/// Every class that will be dirived from this class will support the &lt;b&gt;Clone&lt;/b&gt; method automaticly.&lt;br&gt;

/// The class implements the interface &lt;i&gt;ICloneable&lt;/i&gt; and there for every object that will be derived &lt;br&gt;

/// from this object will support the &lt;i&gt;ICloneable&lt;/i&gt; interface as well.

/// &lt;/summary&gt;

public abstract class BaseObject : ICloneable

   /// &lt;summary&gt;

   /// Clone the object, and returning a reference to a cloned object.

   /// &lt;/summary&gt;

   /// &lt;returns&gt;Reference to the new cloned object.&lt;/returns&gt;

   public object Clone()

   {

    //First we create an instance of this specific type.

    object newObject = Activator.CreateInstance( this.GetType() );

    //We get the array of fields for the new type instance.

    FieldInfo[] fields = newObject.GetType().GetFields();

    int i = 0;

    foreach( FieldInfo fi in this.GetType().GetFields() )

    {    

     //We query if the fiels support the ICloneable interface.

     Type ICloneType = fi.FieldType.GetInterface( "ICloneable" , true );

     if( ICloneType != null )

     {

      //Getting the ICloneable interface from the object.

      ICloneable IClone = (ICloneable)fi.GetValue(this);

      //We use the clone method to set the new value to the field.

      fields[i].SetValue( newObject , IClone.Clone() );

     }

     else

      //If the field doesn't support the ICloneable interface then just set it.

      fields[i].SetValue( newObject , fi.GetValue(this) );

     //Now we check if the object support the IEnumerable interface, so if it does

     //we need to enumerate all its items and check if they support the ICloneable interface.

     Type IEnumerableType = fi.FieldType.GetInterface( "IEnumerable" , true );

     if( IEnumerableType != null )

      //Get the IEnumerable interface from the field.

      IEnumerable IEnum = (IEnumerable)fi.GetValue(this);

      //This version support the IList and the IDictionary interfaces to iterate

      //on collections.

      Type IListType = fields[i].FieldType.GetInterface( "IList" , true );

      Type IDicType = fields[i].FieldType.GetInterface( "IDictionary" , true );

      int j = 0;

      if( IListType != null )

      {

       //Getting the IList interface.

       IList list = (IList)fields[i].GetValue(newObject);

       foreach( object obj in IEnum )

       {

        //Checking to see if the current item support the ICloneable interface.

        ICloneType = obj.GetType().GetInterface( "ICloneable" , true );

        if( ICloneType != null )

        {

         //If it does support the ICloneable interface, we use it to set the clone of

         //the object in the list.

         ICloneable clone = (ICloneable)obj;

         list[j] = clone.Clone();        

        }

        //NOTE: If the item in the list is not support the ICloneable interface then

        // in the cloned list this item will be the same item as in the original list

        //(as long as this type is a reference type).

        j++;

       }

      }

      else if( IDicType != null )

       //Getting the dictionary interface.

       IDictionary dic = (IDictionary)fields[i].GetValue(newObject);

       j = 0;

       foreach( DictionaryEntry de in IEnum )

        //Checking to see if the item support the ICloneable interface.

        ICloneType = de.Value.GetType().GetInterface( "ICloneable" , true );

         ICloneable clone = (ICloneable)de.Value;

         dic[de.Key] = clone.Clone();        

     i++;   

    }

    return newObject;

   }

}

本文轉自 netcorner 部落格園部落格,原文連結:http://www.cnblogs.com/netcorner/archive/2008/06/06/2912136.html  ,如需轉載請自行聯系原作者