首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

.NET 深度複製終極解法:反射實作通用 BaseCopyObject,解決循環引用與事件欄位複製難題

     編輯:關於.NET

.NET 僅提供 MemberwiseClone 淺複製,深複製需自行實作 ICloneable,但手寫 Clone 方法在物件圖複雜、屬性眾多、存在循環引用時極其繁瑣。本文提出反射通用基類 BaseCopyObject,一次解決:

  • 值型別與字串直接複製
  • 參考型別遞迴深複製
  • 循環引用偵測與處理
  • 事件欄位自動排除
  • 非受控資源 (如 DataTable) 可擴充點處理

核心演算法流程

  1. 取得型別 Type 與公用建構子,透過 Activator.CreateInstance 建立新實例 (子類別可覆寫 GetConstructParams 提供建構參數)。
  2. 以 BindingFlags.Instance | Public | NonPublic 列舉所有實例欄位。
  3. 逐欄位處理:
    • 事件欄位:略過 (複製事件委派無意義且危險)。
    • 值型別/字串:直接 GetValue/SetValue。
    • 繼承 BaseCopyObject 的參考型別:遞迴呼叫 CopyObject,傳入 new object[] { this, newObj } 供循環引用判斷。
    • 其他參考型別:呼叫虛擬方法 CopyDefineElement,預設回傳 false 表示無法處理,子類別可覆寫實作自訂複製邏輯 (如 DataTable.Copy())。
  4. 循環引用判斷:若欄位值等於 objs[0] (原始根物件),則直接指向 objs[1] (新根物件),打破循環。

關鍵程式碼片段

public virtual object CopyObject(params object[] objs)
{
    Type t = this.GetType();
    ConstructorInfo[] ctors = t.GetConstructors();
    object newObj = (ctors.Length > 0) ? Activator.CreateInstance(t, GetConstructParams()) : null;

    foreach (FieldInfo fi in t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
    {
        if (t.GetEvent(fi.Name) != null) continue; // 略過事件
        object val = fi.GetValue(this);
        if (val != null)
        {
            Type ft = fi.FieldType;
            if (ft != typeof(string) && !ft.IsValueType)
            {
                if (IsBaseCopyObjectType(ft))
                {
                    if (!CopyDefineElement(ft, ref val))
                    {
                        val = ((BaseCopyObject)val).CopyObject(this, newObj);
                    }
                }
                else
                {
                    CopyDefineElement(ft, ref val); // 子類別處理 DataTable 等
                }
            }
            fi.SetValue(newObj, val);
        }
    }
    return newObj;
}

單元測試驗證

[TestMethod]
public void TestClone()
{
    var a = new ITEmployeeObj();
    var b = new EmployeeObj { EmployeeName = "Tom", ParentObj = a };
    a.Employee = b;
    a.DealEvent += Handler; // 事件欄位

    var clone = (ITEmployeeObj)a.CopyObject();

    Assert.AreNotSame(a, clone);
    Assert.AreNotSame(a.Employee, clone.Employee);
    Assert.AreEqual(a.Employee.EmployeeName, clone.Employee.EmployeeName);
    Assert.AreSame(clone, clone.Employee.ParentObj); // 循環引用正確指向新根
}

擴充點說明

  • GetConstructParams():提供建構子參數,預設 null。
  • CopyDefineElement(Type, ref object):處理非 BaseCopyObject 參考型別,回傳 true 表示已處理。
  • IsBaseCopyObjectType:遞迴檢查型別繼承鏈是否含 BaseCopyObject。

複製架構示意圖

此方案將深複製從「每類手寫」轉為「繼承基類 + 少量覆寫」,大幅降低維護成本,適用於 WinForm 撤銷/重做、物件快照、DTO 複製等高頻場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved