C# 2.0 (ECMA-334) 引入 可空型別,讓實值型別 (int, double, bool, struct 等) 能表示「缺少值」 (null)。語法:在型別後加 ? (如 int?),等同於 Nullable<int>。
int? nullableInt = 1; // 有值
int? nullableInt2 = null; // 無值 (null)
底層 Nullable<T> 是一個 結構,封裝 T Value 與 bool HasValue 兩個欄位。
HasValue:true 表示有實際值,false 表示 null。Value:取得實際值,若 HasValue == false 擲出 InvalidOperationException。Value 屬性 (有風險)。
int? a = 10;
int b = 20;
a = b; // 隱含轉換 int → int?
b = a; // 編譯錯誤:無法隱含轉換 int? → int
b = a.Value; // 執行期若 a 為 null 會擲出例外
// 模式 1:HasValue 檢查
if (a.HasValue) b = a.Value;
// 模式 2:GetValueOrDefault() (C# 2.0 內建)
b = a.GetValueOrDefault(); // 若 null 則回傳 0 (int 預設值)
b = a.GetValueOrDefault(-1); // 自訂預設值
// 模式 3:空合併運算子 ?? (C# 2.0 新增,最推薦)
b = a ?? -1; // a 不為 null 則取 a,否則取 -1
所有預定義一元/二元運算子 (+ - * / % == != > < >= <=) 皆被「提升」為支援可空運算元:
null,算術運算結果為 null;比較運算結果為 false (除 == 與 !=)。==:兩個 null 視為相等 (true);一個 null 一個非 null 為 false。!=:邏輯互補。
int? x = 10;
int? y = null;
int? z = x * y; // z == null
bool eq = (y == null); // true
bool gt = (x > y); // false (y 為 null)
陷阱:將提升運算結果指派給非可空變數會擲出例外:
int? a = null;
int b = a * 2; // 執行期 InvalidOperationException
?? 深度解析語法:result = nullableExpr ?? fallbackExpr。若 nullableExpr 非 null 則取其值,否則取 fallbackExpr。fallbackExpr 型別需能隱含轉換為 result 型別。
int? a = 100;
int? b = null;
int c = a ?? -1; // 100
int d = b ?? -1; // -1
// 串連用法:多層備援
string config = userSetting ?? systemDefault ?? "HardCodedDefault";
DbDataReader.GetInt32 遇 DBNull 擲出例外,改用 int? 配合 ?? 优雅處理。void Foo(int? timeout = null) 並在內部 timeout ??= DefaultTimeout (C# 8.0 ??=)。DateTime? BirthDate 明確表達「未知」而非 DateTime.MinValue。可空型別填補了實值型別無法表達「無值」的語意缺口,配合提升運算子與 ??,讓含有選用性資料的邏輯更清晰、安全。記住:優先用 HasValue/?? 避免直接存取 Value 擲出例外。