不可變型別:建構後狀態凍結,絕不修改。優勢:
GetHashCode() 恆定(原則 10)。Address 是原子型別:街道、城市、州、郵遞區碼強相關,修改其一通常意味著整體變更。Customer 非原子:姓名、地址、電話可獨立變更,應拆解為多個不可變原子型別組合。
struct Address { public string City { get; set; } public string State { get; set; } ... }
var a = new Address { City = "Taipei", State = "TP" };
a.City = "Taichung"; // 此刻 State 仍為 TP → 無效狀態!
a.State = "TXG"; // 修正前,任何線程讀取皆見不一致
多線程下極危險;單線程若拋出例外(郵遞區碼驗證失敗),物件殘留無效狀態。需大量防禦性程式碼修補。
struct Address
{
private readonly string _line1, _line2, _city, _state;
private readonly int _zip;
public string City => _city;
// 僅 getter,無 setter
public Address(string line1, string line2, string city, string state, int zip)
{
_line1 = line1; _line2 = line2; _city = city; _state = state; _zip = zip;
ValidateState(state); ValidateZip(zip); // 建構時一次驗證
}
}
// 使用:建立新實例取代修改
var a = new Address("111 Main", "", "Taipei", "TP", 100);
a = new Address(a._line1, a._line2, "Taichung", "TXG", 400); // 原子性更新
狀態永遠要麼是舊值、要麼是新值,無中間無效狀態。建構失敗拋例外時,原變數保持舊值不變。
// 危險:外部陣列修改影響內部狀態
struct PhoneList { private readonly Phone[] _phones; ... }
// 安全:建構時複製
struct PhoneList
{
private readonly Phone[] _phones;
public PhoneList(Phone[] phones)
{
_phones = new Phone[phones.Length];
phones.CopyTo(_phones, 0); // Phone 為實值型別,複製值
}
// 回傳陣列時同樣複製
public Phone[] Phones => (Phone[])_phones.Clone();
}
若欄位為可變參考型別,建構與回傳時皆需深層複製。
Address)。Color.FromKnownColor())。StringBuilder → string)。勿盲目為所有屬性加 get/set。優先設計為不可變原子實值型別,再組合成複雜結構。簡單、安全、高效。