物件複製是 C# 開發常見需求,但「淺拷貝」與「深拷貝」的行為差異常導致難以除錯的 Bug。本文從記憶體模型(堆疊 vs 堆積)切入,詳細解析 MemberwiseClone、ICloneable、序列化三種實作方式,並提供完整可執行範例。
記憶體模型快速回顧
| 類型分類 | 範例 | 儲存位置 | 指派行為 |
| 值類型 | int, struct, enum | 堆疊 | 複製實際數值 |
| 參考類型 | class, interface, delegate, array | 堆積(物件),堆疊存參考 | 複製參考(指向同一物件) |
淺拷貝
- 定義:建立新物件,逐欄位複製。
- 值類型欄位 → 複製數值(互不影響)。
- 參考類型欄位 → 複製參考(指向同一個堆積物件)。
- 實作:
protected object MemberwiseClone()(Object 內建)。
深拷貝
- 定義:建立新物件,值類型複製數值,參考類型遞迴建立新物件並複製內容。
- 修改新物件的參考型別欄位不影響原物件。
- 常見實作:
- 手動實作
ICloneable.Clone():逐欄位 new 新物件。
- 序列化反序列化:
BinaryFormatter / JsonSerializer / XmlSerializer(需 [Serializable] 或公有無參建構函式)。
- 第三方庫: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
最佳實踐建議
- 預設不可變:類別設計為不可變(唯讀屬性、建構函式注入),根本不需要複製。
- 需要複製時:優先實作
ICloneable 並明確文件說明是淺/深拷貝。
- 複雜物件圖:使用
System.Text.Json 序列化深拷貝,簡單可靠。
- 避免
BinaryFormatter:微軟已標記為過時,存在安全風險。
- 效能關鍵路徑:手寫
Clone 或使用 Source Generator 自動產生。