重訪 C# 空值安全(下): 擁抱 Nullable Reference Types

之前寫過同樣主題的筆記,這次涵蓋 C# 14,更完整,也添加更多細節。分為上下兩集,這是下集。



上一篇文章討論了 Nullable<T> 實值型別以及各種方便好用的 null 運算子(如 ?.  ??),這些工具讓我們能優雅地處理空值。

但這仍然不夠。因為在 C# 8 以前,參考型別(reference types,如 stringList<T>、自訂類別)預設都是可以為 null 的。這意味著變數隨時可能變成地雷。

直到 C# 8.0 引入了 Nullable Reference Types (NRT),我們終於能讓編譯器幫我們把關。

什麼是 Nullable Reference Types?

簡單來說,NRT 讓編譯器開始能夠區分「可以為 null」和「不可為 null」的參考型別。

啟用 NRT 後(.NET 6+ 專案預設啟用),變數的宣告方式發生了根本性的改變:

string name1;       // 不可為 null (Non-nullable)
string? name2;      // 可為 null (Nullable)

注意這裡的 ?:它現在不僅適用於 int? 等實值型別,也適用於參考型別。

編譯器的警告機制

這不是強制性的 runtime 檢查,而是編譯時期的靜態分析。如果你試圖把 null 塞給宣告為不可為 null 的變數,編譯器會警告你:

string name = GetUserName();  // 假設 GetUserName() 回傳 string?
// 警告:可能將 null 參考指派給不可為 null 的參照

反之,如果你要使用一個宣告為 string? 的變數,編譯器會強迫你先檢查:

void PrintName(string? name)
{
    Console.WriteLine(name.Length); // 警告:name 可能為 null
    
    if (name != null)
    {
        Console.WriteLine(name.Length); // 安全!編譯器知道這裡不會是 null
    }
}

! Null 寬容運算子


有時候你比編譯器更清楚狀況。例如,你知道某個欄位雖然宣告為 non-nullable,但會在依賴注入(dependency injection)階段才被填入值:

public class Service
{
    // 告訴編譯器:閉嘴,我知道這裡預設是 null,但我保證使用前它會有值
    public ILogger Logger { get; set; } = null!; 
}

這就是 !(null-forgiving operator)。請務必謹慎使用,因為這等於是關閉了該處的安全檢查。

模式比對:更現代的 Null 檢查

有了 NRT,我們可以搭配 C# 9+ 的模式比對(pattern matching)寫出更語意化的檢查:

// 傳統寫法
if (user == null) return;

// 現代寫法
if (user is null) return;
if (user is not null) { ... }

這樣的寫法不僅讀起來像英文句子,還能避免被自訂的 == 運算子誤導。

甚至可以結合屬性模式(property pattern):

// 只有當 customer 不為 null,且 Orders 也不為 null,且 Count > 0 時才執行
if (customer is { Orders.Count: > 0 })
{
    ProcessOrers(customer);
}

這比起一連串的 ?. 或 && 檢查要清晰得多。


最佳實踐

要在現代 C# 中徹底解決 Null 問題,請遵循以下原則:

  1. 全面啟用 NRT:新專案務必預設開啟 <Nullable>enable</Nullable>。舊專案可以逐檔遷移(使用 #nullable enable)。
  2. 誠實宣告:如果一個參數可能為 null,就加上 ?。這不僅是給編譯器看的,更是給呼叫者看的 API 契約。
  3. 提早檢查(fail fast):在方法的開頭使用 ArgumentNullException.ThrowIfNull(arg) 來攔截非法值,不要讓 null 在系統中流竄。
  4. 善用組合技?.?? 和模式比對是你的好朋友,用它們來消除巢狀的 if

結語

Null reference 曾經是 C# 開發者的夢魘,但隨著語言的演進,我們已經擁有了一套完整的工具來馴服它。從觀念上的轉變(預設不可為 null),到語法上的支援(各種運算子),我們終於可以自信地寫出更安全、更穩固的程式碼。

希望這上下集的兩篇文章能幫助你重新認識 C# 的空值安全機制。

Keep coding!

相關文章

沒有留言:

技術提供:Blogger.
回頂端⬆️