之前寫過同樣主題的筆記,這次涵蓋 C# 14,更完整,也添加更多細節。分為上下兩集,這是上集。
C# 從最初的版本開始,就不斷演進其空值處理機制。從 C# 2.0 的 Nullable Value Types (T?),到各種 null 運算子(??、?.),再到 C# 8 的 Nullable Reference Types,每一步都是為了讓「空值」這件事變得更明確、可追蹤、可由編譯器檢查。
本文要介紹現代 C# 的空值安全機制(null safety),分為上下兩集,涵蓋 C# 14。
這是上集,從基礎觀念與語法糖談起。
為什麼需要空值安全?
在傳統 C# 中,參考型別的變數預設可以是 null:
string name = null; // 完全合法
int length = name.Length; // 運行時爆炸:NullReferenceException
這個問題有多嚴重?NullReferenceException 長期以來都是 .NET 應用程式中最常見的崩潰原因之一。它不僅成本高昂,而且隱蔽性強,往往要等到執行時才爆發。
可為 Null 的實值型別
C# 2.0 引入了可為 Null 的實值型別(Nullable Value Types),也就是
System.Nullable<T> 泛型,讓 int、bool、DateTime 等實值型別也能表達「無值」的狀態。語法:簡潔的 T?
雖然你可以寫 Nullable<int>,但 C# 提供了更簡潔的語法糖 T?,這也是目前推薦的寫法:
Nullable<int> x; // 完整寫法,太囉嗦
int? y; // 簡潔寫法,推薦!
賦值與取值
int? age = null; // 可以是 null
age = 25; // 也可以有值
// 取值方式 1:直接使用
if (age == 25) { ... }
// 取值方式 2:透過 Value 屬性
if (age.Value == 25) { ... } // 注意:若 age 為 null 會拋出異常!
這裡要特別小心:使用 .Value 前,務必先檢查是否為 null(透過 .HasValue 屬性或 != null),否則會拋出 InvalidOperationException。
底層結構:Nullable<T>
Nullable<T> 其實是一個簡單的 struct:
public struct Nullable<T> where T : struct
{
private bool hasValue; // 是否有值
private T value; // 儲存的實際值
// ...
}
這意味著 int? 實際上佔用額外的記憶體來儲存 bool 旗標,且檢查 HasValue 是非常高效的。
寫出優雅的 Null 處理邏輯
了解了基礎的 T? 之後,接下來的問題就是如何「處理」它。
如果你發現自己的程式碼充斥著 if (x != null) 的檢查,那麼 C# 提供了一系列強大的運算子,能讓你把這些冗長的檢查簡化為優雅的單行程式碼。
Null 聯合運算子(??)
Null 聯合運算子(coalescing operator)是最常用的運算子之一,寫法為兩個問號 ?? 。它的語意很直白:「如果左邊不是 null,就用左邊;否則用右邊。」
範例:
// 提供預設值
string name = GetUserName() ?? "訪客";
這行程式碼等同於:
var temp = GetUserName();
string name = (temp != null) ? temp : "訪客";
Null 聯合指派運算子(??=)
Null 聯合指派運算子(coalescing assignment operator)是 C# 8 引入的語法糖,專門處理「如果變數是 null,就給它賦值」的情境,常用於延遲初始化。
範例:private List<string>? _cache;
public List<string> GetCache()
{
// 如果 _cache 是 null,就載入資料並指派給它;否則直接回傳
_cache ??= LoadFromDatabase();
return _cache;
}
Null 條件運算子(?.)
Null 條件運算子(conditional operator)大概是讓 C# 開發者最「有感」的改進(又稱貓王運算子)。有了它,我們可以優雅地處理深層物件存取:
// 只要 customer 或 Order 為 null,結果就是 null
int? orderId = customer?.Order?.Id;
只要鏈條中任何一個環節是 null,整個運算式就會立即短路(short-circuit)並回傳 null,而不會拋出異常。
搭配索引子(
?[]):string firstItem = list?[0]; // 如果 list 為 null,結果就是 null
Null 條件指派(C# 14 新功能)
C# 14 進一步擴展了條件運算子 ?. 的能力,讓它可以放在指派運算子的左邊,稱為 Null 條件指派(conditional assignment)。
範例:
// 只有當 customer 不是 null 時,才執行後面的指派動作
customer?.Order = newOrder;
組合技:現代 C# 的建議寫法
我們可以把上述運算子組合起來,寫出既安全又具備預設值的強大邏輯:
// 若 user 為 null,或 Name 為 null,則回傳 "匿名"
string displayName = user?.Name ?? "匿名";
// 讀取設定,如果中間任何物件為 null,就使用預設值 128
int bufferSize = config?.Settings?.BufferSize ?? 128;
下回預告
有了這些強大的運算子,處理 Null 確實方便多了。但真正的治本之道,是讓編譯器幫我們預防 Null 的產生。
下一篇文章將深入探討 C# 8.0 最具革命性的功能——Nullable Reference Types (NRT),看看如何讓編譯器成為你的空值安全守門員。
Keep learning!
沒有留言: