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

C# 設計不可變值型別:原子性與常量性實踐指南(Effective C# 讀書筆記)

     編輯:關於C#

從類別到結構:資料載體的正確選擇

需求:郵遞地址 (省、市、郵遞區號),郵遞區號必須 6 位數字。

初版用 class:

public class Address
{
    public string Province { get; set; }
    public string City { get; set; }
    private string _zip;
    public string Zip
    {
        get => _zip;
        set { CheckZip(value); _zip = value; }
    }
    private void CheckZip(string v)
    {
        if (!Regex.IsMatch(v, @"^\d{6}$")) throw new Exception("Zip 格式錯誤");
    }
}

問題:

  1. 語意不符:純資料載體應用 struct (值型別),避免參考語意與堆積分配開銷
  2. 資料不一致:多屬性分別設值時,驗證失敗導致部分屬性已修改、部分未修改
  3. 多執行緒競態:讀取時可能看到半更新狀態

解決之道:原子性 + 常量性

  • 原子性:物件狀態為整體,要麼全改、要麼不改 → 建構函式一次性賦值所有欄位
  • 常量性:物件建立後不可變更 → 移除 setter、欄位標記 readonly

改良版:不可變結構

public readonly struct Address
{
    private readonly string _province;
    private readonly string _city;
    private readonly string _zip;

    public Address(string province, string city, string zip)
    {
        _province = province;
        _city = city;
        _zip = zip;
        CheckZip(zip); // 驗證失敗直接拋出,物件不會建立
    }

    public string Province => _province;
    public string City => _city;
    public string Zip => _zip;

    private static void CheckZip(string v)
    {
        if (!Regex.IsMatch(v, @"^\d{6}$")) throw new ArgumentException("郵遞區號需 6 位數字");
    }

    public override string ToString() => $"省:{Province} 市:{City} 郵遞:{Zip}";
}

驗證不可變優勢

var addr = new Address("陝西", "西安", "710068");
try
{
    addr = new Address("山東", "青島", "22233"); // 驗證失敗,addr 保持原值
}
catch { }
Console.WriteLine(addr); // 省:陝西 市:西安 郵遞:710068 ← 狀態一致

陷阱:readonly 參考型別欄位的可變性

若欄位為陣列/List 等參考型別,readonly 僅防止「換掉參考」,不防止「修改內容」:

public readonly struct Address
{
    private readonly string[] _phones;
    public Address(string[] phones) => _phones = phones; // 危險!
    public string[] Phones => _phones;
}

// 外部竄改
var phones = new[] { "029-88401100" };
var addr = new Address(phones);
phones[0] = "029-XXXXXXXX"; // addr 內部陣列被修改!

深層複製防禦:建構函式與 Getter 雙重保護

public readonly struct Address
{
    private readonly string[] _phones;
    public Address(string[] phones)
    {
        _phones = new string[phones.Length];
        phones.CopyTo(_phones, 0); // 建構時深層複製
    }
    public string[] Phones
    {
        get
        {
            var copy = new string[_phones.Length];
            _phones.CopyTo(copy, 0); // 取得時再深層複製
            return copy;
        }
    }
}

現在外部持有的陣列變更完全不影響結構內部資料,反之亦然。

總結三大原則

  1. 純資料聚合、資料量小 → readonly struct (C# 7.2+)
  2. 建構函式集中驗證與賦值,實現原子性
  3. 移除 setter、欄位 readonly、參考型別欄位雙向深層複製,實現常量性

遵循此模式,可獲得執行緒安全、可預測、易於推理的值型別設計。

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