在 C# 開發中,處理資料庫傳回的 null 值是常見挑戰。傳統實值型別(如 int、double)無法直接儲存 null,這導致開發者常需自行設計包裝類別或使用特殊標記值。C# 2.0 引入的「可空型別」機制,從語言層面優雅地解決了這個問題。本文將系統梳理可空型別的聲明方式、核心屬性、比較邏輯、運算行為以及常見陷阱,助你寫出更健壯的程式碼。
可空型別本質上是 System.Nullable<T> 結構的語法糖。以下兩種寫法完全等價:
int? myDBInt = null;
Nullable<int> myDBInt = new Nullable<int>();
兩者皆會建立一個可空整數並初始化為 null。編譯器會自動將 T? 轉換為 Nullable<T>,因此開發者可根據喜好選擇較簡潔的 ? 語法。
每個可空型別實例皆實作 INullableValue 介面,僅暴露兩個唯讀屬性:
bool 型別。若實例持有非 null 值則回傳 true,否則為 false。T 型別。僅在 HasValue == true 時可安全存取;若在 HasValue == false 時強行讀取,將拋出 InvalidOperationException。典型使用模式:
if (myDBInt.HasValue)
Console.WriteLine($"Has a value: {myDBInt.Value}");
else
Console.WriteLine("Does not have a value (NULL)");
除了 HasValue,也可直接與 null 比較,編譯器會自動轉換為 HasValue 檢查:
if (myDBInt != null)
Console.WriteLine($"Has a value: {myDBInt.Value}");
else
Console.WriteLine("Does not have a value (NULL)");
兩種寫法效能相同,純屬程式碼風格偏好。
隱含轉換不存在,必須使用強制轉換或 Value 屬性。若可空值為 null,執行期會拋出 InvalidOperationException。
支援隱含轉換,永遠不會拋出例外,因為非可空實值型別不可能為 null。
直接對可空型別進行關係運算(如 <、>)會產生編譯錯誤,因為 null 無法參與大小比較。正確做法是先檢查 HasValue:
if (myTempDBInt != null)
{
if (myTempDBInt < 100)
Console.WriteLine("myTempDBInt < 100");
else
Console.WriteLine("myTempDBInt >= 100");
}
else
{
// 處理 null 情況
}
這確保只有在擁有實際值時才執行比較邏輯。
可空型別支援與對應非可空型別相同的算術運算子。運算規則如下:
null,結果為 null(傳播性)。null,行為與普通實值型別完全一致。int? dbInt = 10;
int value = 2;
int? result = dbInt + value; // result == 12
int? nullInt = null;
int? nullResult = nullInt + 5; // nullResult == null
這語意符合 SQL 中 NULL 的運算邏輯,極適合資料庫導向的應用場景。
可空型別是 C# 處理「值可能缺失」場景的標準解決方案。掌握其聲明、屬性、轉換、比較與運算規則,能有效避免 NullReferenceException 與 InvalidOperationException,讓程式碼更具表達力與安全性。實務上,建議優先使用 T? 語法,並善用 HasValue 或 null 比較進行防禦性檢查。