需求:郵遞地址 (省、市、郵遞區號),郵遞區號必須 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 格式錯誤");
}
}
問題:
struct (值型別),避免參考語意與堆積分配開銷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 ← 狀態一致
若欄位為陣列/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 內部陣列被修改!
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;
}
}
}
現在外部持有的陣列變更完全不影響結構內部資料,反之亦然。
readonly struct (C# 7.2+)readonly、參考型別欄位雙向深層複製,實現常量性遵循此模式,可獲得執行緒安全、可預測、易於推理的值型別設計。