1. Null 合併運算子 (??)
你對 nullable 型別的理解如果沒有學會怎麼寫簡潔又安全的語法就不算完整。在 C# 裡有兩個超好用的運算子:null 合併 (??) 跟 null 條件運算子 (?.)。
?? 運算子 讓你可以給一個可能是 null 的變數設定「預設值」。
想像一下,你有個使用者名稱,可能是 null。如果真的是 null,你想顯示「訪客」。範例:
string userName = null;
string displayName = userName != null ? userName : "訪客";
用 ?? 可以寫得更精簡:
string userName = null;
string displayName = userName ?? "訪客";
怎麼運作:
- 如果左邊的表達式不是 null,就直接回傳左邊。
- 如果左邊是 null,就用右邊的值。
再來幾個例子:
int? age = null;
int displayAge = age ?? -1; // -1 — 預設值
Console.WriteLine(displayAge); // 會印出 -1
string input = null;
string name = input ?? "訪客";
Console.WriteLine($"哈囉, {name}!"); // 哈囉, 訪客!
運算子串接
?? 可以串成一條鏈:
string result = str1 ?? str2 ?? "預設值";
如果 str1 不是 null,就用它。否則就用 str2,如果連 str2 也是 null,那就用 "預設值"。
2. Null 條件運算子 (?.)
另一個常見情境——只有在物件不是 null 的時候才呼叫方法或存取屬性。範例:
User user = null;
string displayName = user != null ? user.Name : null;
用 ?. 可以更簡單:
User user = null;
string displayName = user?.Name;
如果 user 是 null,這個表達式不會噴錯,只會回傳 null。
範例:
User user = null;
// 沒有 ?. — 會噴錯
// Console.WriteLine(user.Name); // NullReferenceException
Console.WriteLine(user?.Name); // 安全 — 會印出空字串或什麼都沒有
Console.WriteLine(user?.GetProfileInfo()); // 一樣安全
User[] users = null;
int? count = users?.Length; // 如果 users == null,count 也是 null
運算子鏈
你可以串成一整條「鏈」:
string domain = company?.Director?.Email?.Split('@')?[1];
如果路徑上任何一個是 null,整個表達式就會回傳 null,不會噴例外。
跟 ?? 一起用
?. 跟 ?? 超搭:
string display = user?.Name ?? "未知";
如果 user 或 user.Name 是 null,就會顯示「未知」。
3. ! 是什麼?有什麼用?
抑制 null 警告的運算子
在新版 C#(開啟 NRT 時)編譯器會貼心提醒你,如果你用到可能是 null 的變數。但有時你很確定沒問題,這時就可以用 抑制運算子 !。
string? possibleNull = GetUserNameMaybeNull();
Console.WriteLine(possibleNull.Length); // 警告:可能是 null
Console.WriteLine(possibleNull!.Length); // 編譯器不吭聲,但如果真的是 null 會噴例外
重點: ! 不會幫你擋錯誤——它只是跟編譯器說「相信我啦」。如果變數真的等於 null,還是會噴 NullReferenceException。
什麼時候不該用
千萬不要亂用 !。好習慣是盡量少用它。最好讓你的程式碼設計到 null 不可能發生,或是有明確處理。
4. default — 怎麼拿到型別的「預設值」
有時候你只是想把變數「重設」回初始狀態,尤其不想自己手動寫 0、false 或 null。
這時就有 default 關鍵字。
int a = default; // a == 0
bool flag = default; // flag == false
string s = default; // s == null
double? d = default; // d == null
你的小 app 裡可以這樣重設名字或年齡:
userName = default; // null
userAge = default; // null(如果 userAge 是 int?)
5. 差異與細節:?、!、default
就算是老手,有時看到 ?、! 跟 default 也會搞混。來釐清一下誰是誰。
? — 你允許 null
- 值型別:int? x — x 可以是 null。
- 參考型別:string? s — 明確說這變數可以是 null(NRT 模式下)。
! — 你保證不會有 null
- user!.Name — 你跟編譯器保證 user 絕對不是 null。
- 只在 Nullable Reference Types 模式下有用。
default — 你要「預設值」
- int x = default; — x 會變成 0
- string s = default; — s 會變成 null
比較:
| 語法 | 這是什麼? | 哪裡能用? | 跟 null 有啥關係? |
|---|---|---|---|
|
Nullable 值型別 | 到處都能用 | 可以賦值 null |
|
Nullable 參考型別(NRT) | NRT 模式下 | 可以賦值 null |
|
Null-forgiving operator | NRT 模式下 | 抑制警告 |
|
預設值 | 到處都能用 | 參考型別就是 null |
6. 用 ?、! 跟 default 常見錯誤
錯誤 1:以為 ! 能「治癒」null。
其實 ! 只是讓編譯器閉嘴。如果值真的變成 null,程式還是會噴 NullReferenceException。
錯誤 2:用 .Value 前忘了檢查 .HasValue。
特別是 int?、bool? 跟其他 nullable 型別。沒檢查就用會噴 InvalidOperationException。
錯誤 3:在需要「特殊」零值語意時用 default。
比如 0 或 false 可能是有效值,不是「空」的意思。這樣會有邏輯 bug。
錯誤 4:在 NRT 模式下沒用 ? 處理參考型別——或亂用。
有人忘了加 ?,編譯器就會一堆警告。有人則到處亂加 ?,連根本不會有 null 的地方也加。這兩種都會讓程式碼難讀又不穩。
GO TO FULL VERSION