C# 中重載 ++/-- 運算符時,前綴(++x)與後綴(x++)共用同一個實現方法,簽名為:
public static SomeType operator ++(SomeType operand) { ... }
但兩者語義截然不同:前綴返回遞增後的值,後綴返回遞增前的值。編譯器如何用同一方法實現雙重語義?
關鍵在於 編譯器會自動將運算符方法的返回值賦值回原操作數變數。
++x:執行 operator++(x) → 將返回值賦給 x → 表達式值為新 xx++:複製 x 到臨時變數 temp → 執行 operator++(x) → 將返回值賦給 x → 表達式值為 tempclass Counter {
public int Value { get; set; }
public static Counter operator ++(Counter c) {
c.Value++; // 直接修改操作數
return c; // 返回同一引用
}
}
// 測試
var a = new Counter { Value = 1 };
var b = a++; // 預期:a=2, b=1 實際:a.Value=2, b.Value=2 ❌
原因: a++ 先複製引用給 b,再執行 operator++ 修改同一物件,導致 a 與 b 指向同一被修改實例。
public static Counter operator ++(Counter c) {
return new Counter { Value = c.Value + 1 }; // 創建新物件,不修改原值
}
// 測試
var a = new Counter { Value = 1 };
var b = a++; // 正確:a.Value=2, b.Value=1 ✅
此模式符合「運算符不應修改操作數」原則,適用於引用類型與值類型,行為一致可預期。
若 Counter 是 struct,編譯器生成的代碼會進行 逐欄位複製賦值。不可變模式同樣有效,且避免了「修改副本卻賦值回原變數」的困惑。
operand。++/--,改用 Increment() 方法更明確。可以這樣理解:x++ 等價於
var temp = x;
x = x.operator++(x); // 編譯器注入的隱式賦值
// 表達式結果為 temp
這與 C++ 截然不同(C++ 前綴返回引用,後綴返回舊值副本),C# 設計為「統一實現 + 編譯器注入賦值」,簡化了開發者負擔,但要求開發者理解隱式賦值機制。
C# 運算符重載看似簡單,++/-- 的隱式賦值機制卻是無數 Bug 溫床。牢記 「返回新實例,不修改操作數」,配合不可變設計模式,即可避開所有坑點,編寫符合直覺、型別安全的運算符行為。