操作符重載
C# 允許型別把某些運算符定義成特殊方法。對 CLR 來說,+、-、== 這些符號本身不是魔法;真正被記錄在 metadata 裡的是一組有特殊命名規則的方法。
最基本的規則是:
- 必須是
public static - 方法名由編譯器映射成對應的
op_* - 至少有一個參數或回傳型別必須是目前型別
例如 +:
public sealed class Complex
{
public static Complex operator +(Complex c1, Complex c2)
{
return null;
}
}
編譯後,概念上會變成類似下面的 IL 入口:
.method public hidebysig specialname static
class CLR_Ch8.Complex op_Addition(
class CLR_Ch8.Complex c1,
class CLR_Ch8.Complex c2) cil managed
{
IL_0000: ldnull
IL_0001: ret
}
所以當編譯器看到 a + b,它其實是在找能不能綁到某個 op_Addition(...)。
常見對應名稱
理解 CLR 命名之後,看運算符重載就沒那麼神祕了。幾個常見對應如下。
一元運算符
| C# | 特殊方法 |
|---|---|
+x | op_UnaryPlus |
-x | op_UnaryNegation |
!x | op_LogicalNot |
~x | op_OnesComplement |
++x / x++ | op_Increment |
--x / x-- | op_Decrement |
true | op_True |
false | op_False |
二元運算符
| C# | 特殊方法 |
|---|---|
+ | op_Addition |
- | op_Subtraction |
* | op_Multiply |
/ | op_Division |
% | op_Modulus |
& | op_BitwiseAnd |
| | op_BitwiseOr |
^ | op_ExclusiveOr |
<< | op_LeftShift |
>> | op_RightShift |
== | op_Equality |
!= | op_Inequality |
< | op_LessThan |
> | op_GreaterThan |
<= | op_LessThanOrEqual |
>= | op_GreaterThanOrEqual |
什麼情況適合重載
從語法上來說能做,不代表設計上就該做。運算符重載最適合:
- 數值型別
- 向量 / 矩陣 / 複數
- 明確具有數學語意的值物件
如果型別本身不是以「數學運算」為核心語意,硬塞運算符通常只會讓 API 變得更難猜。
一個很實用的判斷標準是:
- 使用者能不能不看文件,就大致猜到
+、-、==的意思 - 這個語意是否穩定、對稱,而且不容易產生副作用
轉換操作符
除了 + 這類運算符,C# 還允許型別定義自訂型別轉換。這其實也是另一組特殊方法。
如果來源型別或目標型別不是編譯器內建能直接處理的轉換,編譯器就會去找對應的 conversion operator。
implicit 與 explicit
implicit:不需要呼叫端手動轉型explicit:需要呼叫端寫出 cast
原則很簡單:
- 不會造成資訊遺失、也不容易讓人驚訝的轉換,才適合
implicit - 可能失真、縮窄、或有語意風險的轉換,用
explicit
public sealed class Rational
{
public Rational(int num) { }
public Rational(float num) { }
public int ToInt() => default;
public float ToFloat() => default;
public static implicit operator Rational(int num)
{
return new Rational(num);
}
public static implicit operator Rational(float num)
{
return new Rational(num);
}
public static explicit operator int(Rational r)
{
return r.ToInt();
}
public static explicit operator float(Rational r)
{
return r.ToFloat();
}
}

編譯器如何處理轉換
當編譯器看到:
Rational r = 10;
int n = (int)r;
它不是直接「懂得」怎麼把 int 變成 Rational,而是會去找:
implicit operator Rational(int num)explicit operator int(Rational r)
找到之後,再在 IL 中生成對應的方法呼叫。
換句話說,自訂轉換的本質不是語言層面的新型別系統,而是由編譯器協助插入的特殊方法呼叫。
is / as 不會使用自訂轉換
這點很重要,也很容易踩錯。
即使你定義了 implicit 或 explicit operator,is 和 as 也不會因此去呼叫它們。
它們檢查的是:
- 參考轉換是否成立
- 裝箱 / 拆箱語意是否成立
- 執行時型別相容性是否成立
而不是「這個型別有沒有自訂寫一個轉換方法」。
小結
操作符方法其實可以濃縮成三句話:
- 運算符重載的底層就是一組
op_*特殊方法。 implicit用於安全自然的轉換,explicit用於可能失真的轉換。is/as不會理會你自訂的 conversion operator。
真正值得追求的不是「把語法玩得多像內建型別」,而是讓型別在語意上真的值得這樣使用。
參考資料
- 《CLR via C#》