C# 表達式

Tip

本文屬於 基礎部分, 適合已經至少懂一種程式語言並正在學習 C# 的開發者。 如果你是程式新手,建議先從 入門 教學開始。

來自另一種語言? C# 中的表達式運作方式與 Java、C++ 和 JavaScript 相似。 值得注意的一個差異是:複合指派運算子如 += 和 increment 運算子 ++ 是 C# 中的表達式,因此可以出現在較大的表達式中。

表達式是編譯器評估以產生值的一段程式碼——數字、字串、參考bool或 。 例如, 3 + 4 * 2 是一個將 值為整數 11的表達式,而 total > 10 是一個將 值為 truefalse的表達式。

最簡單的表達式是 文字 (如 42"hello")和 變數名稱 (如 total)。 你可以透過將較簡單的表達式與運算子結合來構建更複雜的表達式。

表達式和語句

一個表達式產生一個值。 語句是程式執行的完整指令。 許多語句包含表達式。 例如, int total = 3 + 4 * 2; 是一個變數宣告語句。 編譯器會評估初始化運算式 3 + 4 * 2,產生 11,並將該值指派給新變數 total

你可以把一個表達式想像成片語,一個 C# 語句 就像一個完整的句子。 這些工作定義幫助你理解在學習 C# 時會遇到的正式術語和說明,雖然一般程式設計很少需要你有意識地記住這些區別。

組合表達式

表達式可以組合使用。 請考慮 3 + 4 * 2。 這個單一表達式實際上包含兩個較小的表達式: 乘法表達4 * 2 式和 加法表達3 + <result>式。 當表達式組合時,C# 需要一條規則來決定先評估哪一個。 那條規則就是 運算子優先順序

你不需要背誦完整的優先順序。 下一節總結日常群組,括號中通常能讓你在不確定時明確排序。

運算元優先順序

運算子優先順序決定組合運算式如何分組為子運算式。 這個概念類似數學課上的運算順序規則。 優先順序較高的運算子會與其運算元結合得更緊密。 表達式的結構就像是這些運算元被括號括在一起。 在 3 + 4 * 2* ,優先順序高於 +,因此表達式群為 3 + (4 * 2),而非 (3 + 4) * 2

C# 的優先順序群組比這裡總結的四個還多。 這些其他群組遵循大家熟悉的規則——例如乘法先於加法——並涵蓋完整的運算子集。 以下群組涵蓋你在日常程式碼中最常遇到的類型:

  1. 初級、一元和範圍 ——這三個不同的優先順序群,彼此的連結比算術更緊密。 這個摘要將它們合併為一個步驟,因為實際上它們都適用於算術之前。
    • 主要運算子—成員存取(x.y)、方法呼叫(f())、索引(a[i])及 null 條件存取(?.?[])—在所有運算子中結合得最緊密。
    • 一元運算子作用於單一運算元:否定(-x)、邏輯 NOT(!flag)、前綴增量(++i),以及後綴增量(i++)。
    • 範圍運算子(..)用於建立切片表達式的索引範圍,例如 array[1..4]
  2. 算術*/%+- 結合得更緊密。 乘法和除法發生在加法和減法之前。
  3. 比較<><=>===!=的束縛比算術綁定得較寬鬆,因此算術在比較前完成。
  4. 邏輯&&|| 在常見運算子中束縛最不緊密,因此會先完成比較,再進行邏輯組合。

這個優先順序表示,表達式 score + bonus > threshold && attempts < maxAttempts 的求值方式和你讀到的順序完全一致:將 scorebonus 相加,將總和與 threshold 比較,將 maxAttemptsattempts 比較,然後再以 && 結合兩個 bool 的結果。 加上括號使每個分組明確化,結構相同: ((score + bonus) > threshold) && (attempts < maxAttempts)

關於涵蓋所有運算子的完整優先順序階層,請參見 C# 運算子與表達式

用括號來明確表達意圖

括號可以優先處理優先順序,同時記錄你的意圖。 當上述群組的順序不明顯時,請加上括號:

int score = 80;
int bonus = 15;
int threshold = 90;
bool eligible = true;

// Without parentheses: || binds less tightly than &&, so this reads as:
//   eligible && (score + bonus > threshold) || bonus > 20
bool result1 = eligible && score + bonus > threshold || bonus > 20;

// With parentheses: forces the || to combine two complete conditions
bool result2 = (eligible && score + bonus > threshold) || bonus > 20;

Console.WriteLine(result1); // => True
Console.WriteLine(result2); // => True

// Parentheses can also change the result:
bool isAdmin = false;
bool isOwner = true;

// Without: && binds tighter, so: (isAdmin && isOwner) || true
bool access1 = isAdmin && isOwner || true;
// With: forces the || to run first
bool access2 = isAdmin && (isOwner || true);

Console.WriteLine(access1); // => True   (false && true is false; false || true is true)
Console.WriteLine(access2); // => False  (false && anything is false)

最後兩行顯示,括號可以改變結果,而不只是樣式。 當 &&|| 同時出現時,括號內標示哪個條件先結合。 看到 (isAdmin && isOwner) || isSuperUser 的讀者會立刻明白其意圖。

表達式的評估方式

理解 C# 如何評估組合式,使用類比會更容易。 想像用鉛筆和紙在解一個複雜的數學題:你找出最內層或最高優先順序的子表達式,計算其中間值,寫下結果,然後重複下一個子表達式——持續直到得到最終答案。

C# 遵循相同的流程,並遵循兩條規則:

規則一:運算元從左到右評估。 對於任何二元運算式,兩個運算元都必須在套用運算子之前完成求值。 C# 先評估左操作元,再評估右操作元,然後執行操作。

規則二:有些操作員會短路。 某些運算子在結果確定後會立即停止計算,跳過剩餘的運算元:

  • &&(條件 AND):當左側為 false 時,會立即返回 false。 右側從不被評估。
  • ||(條件 OR):當左側變 true時返回true。 右側從不被評估。
  • ?: (條件/三元):只評估符合條件的分支——另一個分支永遠不會被評估。
  • ?.(空條件成員存取)和?[](空條件元素存取):當左側為 null時立即停止並返回null,跳過成員存取或索引。
  • ??= (空合併指派):僅當左側為 null時才指派右側。

紙筆評估

考慮運算式 3 + 6 / 2。 雖然依照讀取順序,加法最先出現,但 / 的優先順序高於 +,因此 6 / 2 是最先被求值的子運算式。 一步步來——就像你在紙上做的一樣:

3 + 6 / 2
      ↓   (evaluate 6 / 2 → 3)
3 +   3
  ↓   (evaluate 3 + 3 → 6)
  6
// 3 + 6 / 2
// Step 1: 6 / 2 has higher precedence → interim value 3
// Step 2: 3 + 3 → final result 6
int result = 3 + 6 / 2;
Console.WriteLine(result); // => 6

6 / 2產生的中間值3成為 的+右運算元,最終結果為 6。 每個子表達式產生一個中間值;這些中間值會餵入下一個子運算式,直到只剩下一個值。

結合性 是一個相關概念:當兩個算子具有相同的優先順序時,結合性決定哪一個先發生。 大多數 C# 運算子都是 左結合,也就是從左到右分組。 因此 a - b - c(a - b) - c相同,不是 a - (b - c)

短路評估的實務

短路求值對於 null 檢查特別有用:

string? text = null;

// Safe: second condition runs only when text is not null
bool hasContent = text != null && text.Length > 0;
Console.WriteLine(hasContent); // => False  (short-circuits after null check; no NullReferenceException)

text = "hello";
hasContent = text != null && text.Length > 0;
Console.WriteLine(hasContent); // => True

// || short-circuits on true: right side is never evaluated when left side is true
string word = "hello";
bool anyMatch = word.StartsWith("h") || word.StartsWith("x");
Console.WriteLine(anyMatch); // => True  (right side never evaluated)

// ?. short-circuits on null: returns null without accessing .Length
string? maybeNull = null;
int? length = maybeNull?.Length;   // length is null; no NullReferenceException
Console.WriteLine(length.HasValue); // => False

text != null && text.Length > 0 是安全的,因為第二個條件只有在 textnull時才會執行。 同理,?. 在遇到 null 參照時會停止評估,從而避免在沒有明確 NullReferenceException 檢查的情況下發生 if

欲更廣泛了解空安全運算子,請參見 C# 空運算子

另請參閱