C# 參考型別指派僅複製參考,雙方指向同一物件。若需獨立副本,必須區分淺層複製(值型別複製值、參考型別複製參考)與深度複製(遞迴建立所有參考物件的新實例)。本文以 MemberwiseClone、ICloneable 為核心,透過極簡範例徹底釐清兩者差異與實作模式。
public class Content { public int Val; }
public class Cloner
{
public Content MyContent = new Content();
public Cloner(int v) => MyContent.Val = v;
public object GetCopy() => MemberwiseClone(); // protected 方法,內部呼叫
}
測試:
var src = new Cloner(10);
var dst = (Cloner)src.GetCopy();
Console.WriteLine(dst.MyContent.Val); // 10
src.MyContent.Val = 15;
Console.WriteLine(dst.MyContent.Val); // 15 ← 同一 Content 實例!
結論:MemberwiseClone 只複製當前物件的欄位位元,參考型別欄位仍指向同一實例。
public class Cloner : ICloneable
{
public Content MyContent = new Content();
public Cloner(int v) => MyContent.Val = v;
public object Clone()
{
// 手動建立新實例,遞迴複製參考物件
var cloned = new Cloner(MyContent.Val); // 新 Content 隨新 Cloner 建立
return cloned;
}
}
測試:
var src = new Cloner(10);
var dst = (Cloner)src.Clone();
Console.WriteLine(dst.MyContent.Val); // 10
src.MyContent.Val = 15;
Console.WriteLine(dst.MyContent.Val); // 10 ← 獨立實例!
public class Inner : ICloneable
{
public int Data;
public object Clone() => new Inner { Data = this.Data };
}
public class Outer : ICloneable
{
public Inner InnerObj = new Inner();
public object Clone()
{
var cloned = new Outer();
cloned.InnerObj = (Inner)InnerObj.Clone(); // 遞迴呼叫
return cloned;
}
}
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| ICloneable + 手動遞迴 | 標準介面、完全控制 | 樣板代碼多、易漏欄位 | 領域模型、DTO、需精確語意 |
| 序列化反序列化 (JSON/Binary) | 一行代碼、自動處理巢狀 | 效能較差、需可序列化、忽略非公有欄位 | 快速原型、跨進程傳遞 |
| Expression Tree / IL Emit 自動深拷貝 | 高效能、零樣板 | 複雜度高、需第三方庫 (FastDeepCloner 等) | 高頻複製、大型物件圖 |
| Record / with 運算式 (C# 9+) | 不可變、語法級支援 | 僅限 record 型別 | 新專案、資料導向模型 |
record、readonly struct、init-only 屬性,從根源避免複製需求。ICloneable 並明確文件化「淺/深」語意;或使用 JsonSerializer.Serialize/Deserialize 快速深拷貝(注意循環參考)。
3. 避免直接使用 MemberwiseClone 對外暴露:它是 protected 方法,語意為「淺層複製」,外部呼叫者易誤以為深度複製。
4. 效能敏感路徑:引入 FastDeepCloner 或自寫 IL 複製器,避免反射開銷。
MemberwiseClone = 淺層複製(位元複製欄位),ICloneable.Clone = 您定義的語意(通常是深度複製)。理解「值型別複製值、參考型別複製參考」這一底層機制,即可在任何複製場景做出正確選擇。現代 C# 建議優先採用不可變資料模型,讓複製問題自然消失。