C#中實現任意類的完美克隆
阿新 • • 發佈:2019-02-05
簡介
雖然在現實世界中的克隆課題是有爭議的, 在.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; } }
注:本文來自網路收集。
《完》