運算子 是告訴編譯器執行特定運算的符號。C# 提供豐富的內建運算子,分為六大類。理解運算子語意、優先序、副作用、溢位行為,是寫出正確、高效程式碼的基礎。
| 類別 | 運算子 | 說明 |
|---|---|---|
| 算術 | + - * / % | 加減乘除取模 |
++ -- | 遞增遞減 (前綴/後綴) | |
+ - | 一元正負號 | |
| 關係 | == != < > <= >= | 相等/不等/大小比較 |
| 邏輯 | && || ! | 邏輯與/或/非 (短路) |
& | ^ | 位元與/或/異或 (非短路,也用於 bool) | |
| 位元 | ~ | 位元補數 (翻轉) |
<< >> | 左移/右移 | |
| 指派 | = | 簡單指派 |
+= -= *= /= %= | 複合指派 | |
<<= >>= &= |= ^= | 位元複合指派 | |
| 其他 | ?. ?[] ?? ??= | Null 條件/合併 |
?: | 三元條件 | |
is as | 型別測試/轉換 | |
sizeof typeof nameof | 大小/型別/名稱 | |
new default delegate | 建立/預設/委派 | |
-> => | Lambda/運算式主體 |
// 預設 unchecked context:溢位環繞 (不拋例外)
int max = int.MaxValue; // 2,147,483,647
var overflow = max + 1; // -2,147,483,648
// checked context:溢位拋 OverflowException
checked {
var overflow2 = max + 1; // 擲出例外
}
// 或編譯器選項 /checked+
int a = 5;
int b = a++; // b=5, a=6 (後綴:先用後增)
int c = ++a; // c=7, a=7 (前綴:先增後用)
int d = 5;
int e = d--; // e=5, d=4
int f = --d; // f=3, d=3
int a = 7, b = 3;
a / b; // 2 (整數除法捨去小數)
a % b; // 1 (餘數)
// 負數取模:結果符號同被除數
-7 % 3; // -1
7 % -3; // 1
-7 % -3; // -1
string a = "hello", b = "hello";
a == b; // true (字串多載 == 比較內容)
object o1 = a, o2 = b;
o1 == o2; // true (字串仍比較內容)
var sb1 = new StringBuilder("x");
var sb2 = new StringBuilder("x");
sb1 == sb2; // false (預設參考比較,未多載 ==)
double x = 0.1 + 0.2; // 0.30000000000000004
x == 0.3; // false!
// 正確:容差比較
bool AlmostEqual(double a, double b, double epsilon = 1e-10)
=> Math.Abs(a - b) < epsilon;
bool a = true, b = false;
a && b; // false (短路:b 不執行)
a || b; // true (短路:b 不執行)
// 利用短路避免 NullReference
if (user != null && user.IsActive) { } // user 為 null 時不存取 IsActive
// 非短路版本 (& |) - 兩邊皆執行
if (user != null & user.IsActive) { } // 危險:user 為 null 仍存取 IsActive
[Flags] enum Permissions { None=0, Read=1, Write=2, Execute=4, Admin=8 }
var p = Permissions.Read | Permissions.Write; // 3 (Read+Write)
p.HasFlag(Permissions.Read); // true
// 設定旗標
p |= Permissions.Execute; // 加入 Execute
// 移除旗標
p &= ~Permissions.Write; // 移除 Write
// 切換旗標
p ^= Permissions.Admin; // 反轉 Admin
// 2 的冪次檢查
bool IsPowerOfTwo(int n) => n > 0 && (n & (n - 1)) == 0;
// 取模優化 (2 的冪次)
int ModPowerOfTwo(int x, int m) => x & (m - 1); // m 必須是 2 的冪次
// 交換兩數 (無暫存變數)
a ^= b; b ^= a; a ^= b;
// 快速乘除 2
x << 1; // x * 2
x >> 1; // x / 2 (算術右移,保留符號)
int x = 10;
x += 5; // x = x + 5
x -= 3; // x = x - 3
x *= 2; // x = x * 2
x /= 4; // x = x / 4
x %= 3; // x = x % 3
x <<= 1; // x = x << 1
x &= 0xF; // x = x & 0xF
// 複合指派於屬性/索引子:只評估一次左邊
list[GetIndex()] += 1; // GetIndex() 只呼叫一次
| 運算子 | 名稱 | 範例 | 說明 |
|---|---|---|---|
?. | Null 條件成員存取 | user?.Name | user 為 null 回傳 null,否則存取 Name |
?[] | Null 條件索引 | arr?[0] | arr 為 null 回傳 null |
?? | Null 合併 | x ?? "default" | x 為 null 回傳右邊 |
??=` | Null 合併指派 | x ??= "default" | x 為 null 才指派 |
is / as / ?:`// 型別測試 + 模式比對
if (obj is int i) { }
if (obj is string { Length: > 5 }) { }
// 安全轉換
string s = obj as string; // 失敗回傳 null (僅參考型別)
// 等同
string s2 = obj is string str ? str : null;
// 三元運算子
var result = condition ? valueIfTrue : valueIfFalse;
sizeof / typeof / nameofsizeof(int); // 4 (編譯期常數,需 unsafe context 除非內建型別)
typeof(string); // System.Type 物件
nameof(Console); // "Console" (編譯期字串,重構安全)
高優先序先執行。同優先序依結合性 (大多左結合,指派/一元/三元右結合)。
| 優先序 (高→低) | 運算子 | 結合性 |
|---|---|---|
| 1 | x.y f(x) a[x] x++ x-- new typeof sizeof checked unchecked | 左 |
| 2 | + - ! ~ ++x --x (T)x await | 右 |
| 3 | * / % | 左 |
| 4 | + - | 左 |
| 5 | << >> | 左 |
| 6 | < > <= >= is as | 左 |
| 7 | == != | 左 |
| 8 | & | 左 |
| 9 | ^ | 左 |
| 10 | | | 左 |
| 11 | && | 左 |
| 12 | || | 左 |
| 13 | ?: ?? | 右 |
| 14 | = += -= *= /= %= &= |= ^= <<= >>= ??=` | 右 |
int a = 10, b = 20, c = 30;
// 1. && 優先於 ||
bool r1 = a > 5 || b < 10 && c == 30; // a>5 (true) 短路 -> true
// 2. ?: 右結合
int r2 = a > b ? 1 : a > c ? 2 : 3; // a>b? 1 : (a>c? 2 : 3)
// 3. 指派右結合
int x, y, z;
x = y = z = 5; // x = (y = (z = 5))
// 4. 建議加括號提升可讀性
bool r3 = (a > 5) || ((b < 10) && (c == 30));
obj != null && obj.Method() 習慣用法。x = y++ + z++ 難閱讀,拆行。a?.b?.c ?? default 簡潔安全。a += b 等同 a = a + b 嗎?A: 大多數情況是,但 a 為屬性/索引子時,+= 只評估一次左邊 (避免重複 getter/setter 呼叫)。
&& vs & 什麼時候用哪個?A: 幾乎永遠用 && (短路)。& 用於位元運算、或罕見需兩邊副作用皆執行時。
>> 是算術還是邏輯?A: 有號整數為算術右移 (保留符號位),無號整數為邏輯右移 (補 0)。