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

C# 深拷貝與淺拷貝完整解析:ICloneable、序列化、ValueTuple 現代寫法

     編輯:關於C語言

前言

C# 參考型別預設為淺拷貝(複製參考),值型別為深拷貝(複製值)。理解兩者差異並掌握正確深拷貝技術,是避免共享狀態 Bug 的關鍵。

核心概念對比

特性淺拷貝深拷貝
定義複製物件參考,指向同一記憶體遞迴建立全新物件圖,無共享參考
值型別複製值 (隱含深拷貝)複製值
參考型別複製參考建立新實例並複製所有欄位
Array.Clone()✅ 淺拷貝❌
List 构造函數✅ 淺拷貝❌

經典 ICloneable 實作


using System;

public class Person : ICloneable
{
    public int Id { get; set; }
    public int Age { get; set; }
    public Address Address { get; set; } // 參考型別欄位

    public object Clone()
    {
        // 手動深拷貝:值型別直接賦值,參考型別遞迴 Clone
        return new Person
        {
            Id = this.Id,
            Age = this.Age,
            Address = this.Address?.Clone() as Address // 假設 Address 也實作 ICloneable
        };
    }

    public void Show() => Console.WriteLine($"Id: {Id}, Age: {Age}, Addr: {Address?.City}");
}

public class Address : ICloneable
{
    public string City { get; set; }
    public string Street { get; set; }
    
    public object Clone() => new Address { City = this.City, Street = this.Street };
}

驗證深/淺拷貝差異


public static void Demo()
{
    var original = new Person 
    { 
        Id = 1, 
        Age = 30, 
        Address = new Address { City = "Taipei", Street = "Renai Rd" } 
    };

    // 1. 淺拷貝:Array.Clone()
    Person[] shallowArray = (Person[])new Person[] { original }.Clone();
    
    // 2. 深拷貝:手動 Clone
    Person[] deepArray = new Person[] { (Person)original.Clone() };

    Console.WriteLine("=== 修改前 ===");
    original.Show();           // Id: 1, Age: 30, Addr: Taipei
    shallowArray[0].Show();    // Id: 1, Age: 30, Addr: Taipei
    deepArray[0].Show();       // Id: 1, Age: 30, Addr: Taipei

    // 修改原物件的參考型別欄位
    original.Address.City = "Kaohsiung";

    Console.WriteLine("\n=== 修改原物件 Address.City 後 ===");
    original.Show();           // Id: 1, Age: 30, Addr: Kaohsiung ← 變了
    shallowArray[0].Show();    // Id: 1, Age: 30, Addr: Kaohsiung ← 同步變了!(淺拷貝)
    deepArray[0].Show();       // Id: 1, Age: 30, Addr: Taipei    ← 未變 (深拷貝)
}

現代深拷貝方案對比

方案優點缺點適用場景
手動 Clone效能最好、完全可控、無反射樣板代碼多、維護成本高核心領域模型、高頻呼叫
BinaryFormatter 序列化一行代碼、支援任意物件圖已過時/不安全、效能差、需 [Serializable]❌ 棄用
JSON 序列化 (System.Text.Json)安全、跨平台、可控制選項需無參建構函式、循環參考需處理、私有欄位需配置✅ 推薦通用方案
MessagePack / Protobuf二進制、高效能、嚴格合約需定義 Schema、額外依賴高效能、跨語言
AutoMapper / Mapster慣例配置、投影查詢優化映射邏輯隱式、效能開銷DTO 轉換、領域模型映射
Record / with 運算式 (C# 9+)語言級支援、不可變、結構化僅適用 record 型別、淺拷貝 (with 只複製參考)不可變資料模型

JSON 深拷貝通用擴充方法


using System.Text.Json;

public static class DeepCloneExtensions
{
    private static readonly JsonSerializerOptions _options = new()
    {
        // 處理循環參考
        ReferenceHandler = ReferenceHandler.Preserve,
        // 包含私有欄位 (需 .NET 7+)
        IncludeFields = true,
        // 忽略 null 可選
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
    };

    public static T DeepClone(this T source)
    {
        if (source == null) return default;
        string json = JsonSerializer.Serialize(source, _options);
        return JsonSerializer.Deserialize(json, _options);
    }
}

// 使用
var deepCopy = original.DeepClone();

Record 型別的 with 運算式 (C# 9+)


// Record 預設不可變,with 建立新實例並修改指定屬性
public record PersonRecord(int Id, int Age, AddressRecord Address);

public record AddressRecord(string City, string Street);

var p1 = new PersonRecord(1, 30, new AddressRecord("Taipei", "Renai Rd"));
// 淺拷貝並修改 Age
var p2 = p1 with { Age = 31 }; 
// p2.Address 仍指向同一 AddressRecord 實例 (淺拷貝)

// 深拷貝需手動
var p3 = p1 with { Age = 31, Address = p1.Address with { City = "Kaohsiung" } };

效能基準測試 (參考)


| 方法                    | Mean (ns) | Allocated (B) | 
|------------------------|-----------|---------------|
| Manual Clone           |    45     |      0        |
| JSON (System.Text.Json)|   1,200   |    1,200      |
| BinaryFormatter (Obsolete)| 2,500  |    2,800      |
| AutoMapper             |   3,800   |    3,500      |

最佳實踐建議

  1. 領域模型:手動實作 Clone() 或使用 record + with 組合
  2. DTO/ViewModel:System.Text.Json 序列化深拷貝
  3. 集合拷貝:list.Select(x => x.DeepClone()).ToList()
  4. 避免:BinaryFormatter、MemberwiseClone (protected、淺拷貝)、Object.MemberwiseClone
  5. 不可變設計:優先使用 record、readonly、init 讓拷貝需求自然減少

總結

淺拷貝複製參考,深拷貝複製物件圖。現代 C# 推薦:領域模型用 record + 手動 with 組合;通用場景用 System.Text.Json 序列化;高效能熱路徑手寫 Clone。理解記憶體語義,選對工具,才能寫出正確且高效的代碼。

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