雖然在現實世界中的克隆課題是有争議的, 在.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;
}
}