1. 程式人生 > >C#中實現任意類的完美克隆

C#中實現任意類的完美克隆

簡介

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

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

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

  • 檢視一下類中的每一個欄位是否支援ICloneable介面
    如果某欄位不支援ICloneable介面,那麼該欄位將以常規方式處理。這意味著,如果該欄位是一個值型別,那麼該值被拷貝;如果該欄位是一個引用型別,克隆的欄位將指向同一個物件。
    如果該欄位支援ICloneable介面,我們將使用其本身的Clone方法對其進行克隆。
    如果該欄位支援IEnumerable介面,我們需要檢查他是否支援IList 或 IDictionary 介面。如果支援,那麼我們迭代該集件,並且檢視集合的每一項是否支援Cloneable介面。

如何使用

讓你的類支援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 介面,克隆方法將偵測該欄位,輪詢所有項,並同樣地試圖對他們進行克隆。

BaseObject類的完整實現程式碼

/// 
/// BaseObject類是一個用來繼承的抽象類。 
/// 每一個由此類繼承而來的類將自動支援克隆方法。
/// 該類實現了Icloneable介面,並且每個從該物件繼承而來的物件都將同樣地
/// 支援Icloneable介面。 
///  
public abstract class BaseObject : ICloneable
{
    /// 
    /// 克隆物件,並返回一個已克隆物件的引用    
    ///     
    /// 引用新的克隆物件
    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);
                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;
    }
}

注:本文來自網路收集。

《完》