天天看點

c#中對象的克隆

雖然在現實世界中的克隆課題是有争議的, 在.NET世界使用它卻足夠安全, 難道不是嗎?

為實作一個類你究竟有多少次要實作ICloneable接口, 而且每一次都寫相同的代碼,或為每個類寫特定的代碼。而且,當你的類加入一個新的字段時,往往會忘記更新這個新字段的克隆方法。如果我沒說錯的話,這種時候往往會帶來惱人的bugs。

這是我的類得以存在的原因。 藉由反射機制的小小幫助,我建立了一個用預設行為實作了ICloneable接口的抽象類。現在或許你正在問自己: 什麼是預設行為? 那麼我很高興你這樣詢問。 克隆的預設行為,是采用以下的規則來克隆類中的每一個字段:

檢視一下類中的每一個字段是否支援ICloneable接口

如果某字段不支援ICloneable接口,那麼該字段将以正常方式處理。這意味着,如果該字段是一個值類型,那麼該值被拷貝;如果該字段是一個引用類型,克隆的字段将指向同一個對象。

如果該字段支援ICloneable接口,我們将使用其本身的Clone方法對其進行克隆。

如果該字段支援IEnumerable接口,我們需要檢查他是否支援IList 或 IDictionary 接口。如果支援,那麼我們疊代該集件,并且檢視集合的每一項是否支援ICloneable接口。

如何使用

讓你的類支援Icloneable接口所要做的就是,将你的類繼承自如下所述的BaseObject類:

public class MyClass : BaseObject

{   

    public string myStr =”test”;   

    public int id;

}

public class MyContainer : BaseObject

{   

    public string name = “test2”;

    public MyClass[] myArray= new MyClass[5];

    public class MyContainer() 

   { 

       for(int i=0 ; i<5 ; i++)

       {

             this.myArray[I] = new MyClass(); 

       }

    }

}

現在在Main方法中加入如下代碼:

static void Main(string[] args)

{   

    MyContainer con1 = new MyContainer();

    MyContainer con2 = (MyContainer)con1.Clone(); 

   con2.myArray[0].id = 5;

}

當監測con2執行個體時,你将會看到MyClass執行個體的第一項已經變為5,而con1執行個體卻沒有改變。這樣你将明白加入到類中的任意支援ICloneable接口的字段将被同樣地克隆。而且,如果該字段支援IList 或 IDictionary 接口,克隆方法将偵測該字段,輪詢所有項,并同樣地試圖對他們進行克隆。

實作

/// <summary>

/// BaseObject類是一個用來繼承的抽象類。

/// 每一個由此類繼承而來的類将自動支援克隆方法。

/// 該類實作了Icloneable接口,并且每個從該對象繼承而來的對象都将同樣地

/// 支援Icloneable接口。

/// </summary>

public abstract class BaseObject : ICloneable

{   

/// <summary>   

/// 克隆對象,并傳回一個已克隆對象的引用   

/// </summary>   

/// <returns>引用新的克隆對象</returns>    

public object Clone()

{

//首先我們建立指定類型的一個執行個體

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

//我們取得新的類型執行個體的字段數組。

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

int i = 0;        

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

{           

//我們判斷字段是否支援ICloneable接口。

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

if( ICloneType != null )

{

//取得對象的Icloneable接口。

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

//我們使用克隆方法給字段設定新值。

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

}

Else

{

// 如果該字段不支援ICloneable接口,直接設定即可。

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

}            

//現在我們檢查該對象是否支援IEnumerable接口,如果支援,

//我們還需要枚舉其所有項并檢查他們是否支援IList 或 IDictionary 接口。

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

if( IEnumerableType != null )

{

//取得該字段的IEnumerable接口

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

//這個版本支援IList 或 IDictionary 接口來疊代集合。

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

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

int j = 0;

if( IListType != null )

{

    //取得IList接口。

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

    foreach( object obj in IEnum )

    {    

        //檢視目前項是否支援支援ICloneable 接口。

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

         if( ICloneType != null )

            {

                //如果支援ICloneable 接口,   

//我們用它來設定清單中的對象的克隆

ICloneable clone = (ICloneable)obj;

list[j] = clone.Clone();

}

//注意:如果清單中的項不支援ICloneable接口,那麼

                     //在克隆清單的項将與原清單對應項相同

                  //(隻要該類型是引用類型)

                 j++;

                 }

             }               

else if( IDicType != null )

{   

//取得IDictionary 接口

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

j = 0;

  foreach( DictionaryEntry de in IEnum )

   {

       //檢視目前項是否支援支援ICloneable 接口。

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

     if( ICloneType != null )

    {

        ICloneable clone = (ICloneable)de.Value;

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

    }

j++;

         }

   }

}

i++;

}

return newObject;

}

}