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

C# 淺拷貝與深拷貝完整解析:MemberwiseClone、ICloneable 與序列化實作

     編輯:關於C#

物件複製是 C# 開發常見需求,但「淺拷貝」與「深拷貝」的行為差異常導致難以除錯的 Bug。本文從記憶體模型(堆疊 vs 堆積)切入,詳細解析 MemberwiseClone、ICloneable、序列化三種實作方式,並提供完整可執行範例。

記憶體模型快速回顧

類型分類範例儲存位置指派行為
值類型int, struct, enum堆疊複製實際數值
參考類型class, interface, delegate, array堆積(物件),堆疊存參考複製參考(指向同一物件)

淺拷貝

  • 定義:建立新物件,逐欄位複製。
  • 值類型欄位 → 複製數值(互不影響)。
  • 參考類型欄位 → 複製參考(指向同一個堆積物件)。
  • 實作:protected object MemberwiseClone()(Object 內建)。

深拷貝

  • 定義:建立新物件,值類型複製數值,參考類型遞迴建立新物件並複製內容。
  • 修改新物件的參考型別欄位不影響原物件。
  • 常見實作:
  1. 手動實作 ICloneable.Clone():逐欄位 new 新物件。
  2. 序列化反序列化:BinaryFormatter / JsonSerializer / XmlSerializer(需 [Serializable] 或公有無參建構函式)。
  3. 第三方庫:AutoMapper、FastDeepCloner 等。

程式碼範例:淺拷貝示範

using System;

class Car { public string Name; public Car(string n) => Name = n; }

class Person
{
    public int Id;
    public string Name;
    public Car Car;

    public Person(int id, string name, Car car) { Id = id; Name = name; Car = car; }

    // 對外提供淺拷貝
    public object Clone() => MemberwiseClone();
}

class Demo
{
    static void Main()
    {
        Person p1 = new Person(1, "Scott", new Car("BMW"));
        Person p2 = (Person)p1.Clone(); // 淺拷貝

        Console.WriteLine("修改 p1 後:");
        p1.Id = 2;
        p1.Name = "Lacy";
        p1.Car.Name = "紅旗"; // 修改參考型別欄位指向的物件

        Console.WriteLine($"p1: Id={p1.Id}, Name={p1.Name}, Car={p1.Car.Name}");
        Console.WriteLine($"p2: Id={p2.Id}, Name={p2.Name}, Car={p2.Car.Name}");
        // 輸出:p2.Car.Name 也變成「紅旗」→ 參考同一 Car 物件
    }
}

深拷貝實作比較

方式優點缺點適用場景
手動 Clone效能最好、完全可控維護成本高、易遺漏欄位少數關鍵類別、效能敏感
BinaryFormatter 序列化一行程式碼處理複雜物件圖效能差、已過時(.NET 5+ 不建議)、需 [Serializable]舊專案快速原型
JSON 序列化 (System.Text.Json)跨平台、人類可讀、無需屬性需公有無參建構函式、循環參考需處理、私有欄位需設定現代 .NET 專案首選
Expression Tree / IL Emit 庫效能接近手動、自動化引入依賴、學習曲線大量 DTO/Entity 複製

JSON 深拷貝範例 (.NET 6+)

using System.Text.Json;

public static T DeepClone(T obj)
{
    var json = JsonSerializer.Serialize(obj);
    return JsonSerializer.Deserialize(json);
}

// 使用
var p3 = DeepClone(p1);
p3.Car.Name = "Toyota"; // 不影響 p1.Car

最佳實踐建議

  1. 預設不可變:類別設計為不可變(唯讀屬性、建構函式注入),根本不需要複製。
  2. 需要複製時:優先實作 ICloneable 並明確文件說明是淺/深拷貝。
  3. 複雜物件圖:使用 System.Text.Json 序列化深拷貝,簡單可靠。
  4. 避免 BinaryFormatter:微軟已標記為過時,存在安全風險。
  5. 效能關鍵路徑:手寫 Clone 或使用 Source Generator 自動產生。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved