筆記目錄

Skip to content

使用 CallerArgumentExpression 簡化參數檢核

TLDR

  • 使用 [CallerArgumentExpression] 屬性可以自動擷取傳入參數的變數名稱,無需再透過 Expression 處理。
  • 相比於使用 Expression<Func<T>> 的舊做法,此方法效能更好且程式碼更簡潔。
  • 配合 [NotNull][DoesNotReturnIf] 等屬性,能讓編譯器正確識別 Nullable reference type 的狀態,避免不必要的警告。
  • 建議優先使用 .NET 6/7 內建的 ArgumentNullException.ThrowIfNull 等靜態方法,若需自定義檢核邏輯,則參考 [CallerArgumentExpression] 實作。

傳統 Expression 檢核法的問題

什麼情況下會遇到這個問題:當開發者為了簡化參數檢核,使用 Expression 來避免同時傳入參數值與參數名稱時。

在舊有的實作中,為了簡化參數檢查,通常會撰寫如下的工具類:

csharp
public static class ExceptionUtils {
    public static void ThrowIfNull<T>(Expression<Func<T?>> expression) {
        _ = expression.Compile().Invoke()
            ?? throw new ArgumentNullException(GetMemberName(expression));
    }

    private static string GetMemberName<T>(Expression<Func<T>> expression) {
        if (expression.Body is not MemberExpression expressionBody) {
            throw new ArgumentException("Expression 表達式錯誤。", nameof(expression));
        }
        return expressionBody.Member.Name;
    }
}

此做法雖然達到了簡化呼叫的目的,但存在以下缺點:

  • 效能開銷:Expression 的編譯與執行比直接存取變數慢。
  • 編譯器支援度:無法在參數上標記 [NotNull],導致編譯器無法識別檢查後的變數狀態,進而產生 Nullable reference type 的警告。

使用 CallerArgumentExpression 優化檢核

什麼情況下會遇到這個問題:當需要自定義參數檢核邏輯,且希望同時獲得編譯器對 Nullable reference type 的支援與簡潔的語法時。

透過 C# 10 引入的 [CallerArgumentExpression] 屬性,我們可以自動取得參數的變數名稱,而無需使用 Expression。以下是優化後的實作方式:

csharp
public static void ThrowIfNull([NotNull] object? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null) {
    if (argument is null) {
        throw new ArgumentNullException(paramName);
    }
}

驗證結果

測試程式碼如下:

csharp
string? str = "";
Console.Write("未傳入 paramName 時,");
TestCallerArgumentExpression(str);

Console.Write("有傳入 paramName 時,");
TestCallerArgumentExpression(str, "str2");

void TestCallerArgumentExpression(object? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null) {
    Console.WriteLine("paramName:" + paramName);
}

執行結果:

text
未傳入 paramName 時,paramName: str
有傳入 paramName 時,paramName: str2

此方法不僅能自動補全變數名稱,還能透過 [NotNull] 標記,讓編譯器在呼叫該方法後,正確判斷變數是否為 null

結論與建議

  • 優先使用官方 API:.NET 6 與 .NET 7 已經內建了 ArgumentNullException.ThrowIfNullArgumentException.ThrowIfNullOrWhiteSpace 等方法,應優先使用這些標準庫。
  • 自定義檢核:若需實作特殊的檢核邏輯,請使用 [CallerArgumentExpression] 取代 Expression,以提升效能並獲得完整的編譯器檢查支援。

異動歷程

  • 2024-10-13 初版文件建立。