On this page

Skip to content

Simplifying Parameter Validation with CallerArgumentExpression

TLDR

  • Use the [CallerArgumentExpression] attribute to automatically capture the variable name of an incoming parameter, eliminating the need to process it via Expression.
  • Compared to the old approach using Expression<Func<T>>, this method offers better performance and cleaner code.
  • Combined with attributes like [NotNull] or [DoesNotReturnIf], it allows the compiler to correctly identify the state of Nullable reference types, avoiding unnecessary warnings.
  • It is recommended to prioritize the built-in static methods in .NET 6/7, such as ArgumentNullException.ThrowIfNull. If custom validation logic is required, implement it using [CallerArgumentExpression].

Problems with the Traditional Expression Validation Method

When does this issue arise? When developers use Expression to simplify parameter validation, aiming to avoid passing both the parameter value and the parameter name simultaneously.

In older implementations, to simplify parameter checks, one would typically write a utility class like this:

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 expression error.", nameof(expression));
        }
        return expressionBody.Member.Name;
    }
}

Although this approach achieves the goal of simplifying calls, it has the following drawbacks:

  • Performance overhead: Compiling and executing an Expression is slower than direct variable access.
  • Compiler support: It is impossible to mark parameters with [NotNull], preventing the compiler from recognizing the state of the variable after the check, which leads to Nullable reference type warnings.

Optimizing Validation Using CallerArgumentExpression

When does this issue arise? When you need custom parameter validation logic and want both compiler support for Nullable reference types and concise syntax.

Through the [CallerArgumentExpression] attribute introduced in C# 10, we can automatically obtain the variable name of a parameter without using Expression. Here is the optimized implementation:

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

Verification Results

The test code is as follows:

csharp
string? str = "";
Console.Write("When paramName is not passed, ");
TestCallerArgumentExpression(str);

Console.Write("When paramName is passed, ");
TestCallerArgumentExpression(str, "str2");

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

Execution results:

text
When paramName is not passed, paramName: str
When paramName is passed, paramName: str2

This method not only automatically fills in the variable name but also allows the compiler to correctly determine whether the variable is null after calling the method by using the [NotNull] attribute.

Conclusion and Recommendations

  • Prioritize official APIs: .NET 6 and .NET 7 have built-in methods like ArgumentNullException.ThrowIfNull and ArgumentException.ThrowIfNullOrWhiteSpace; these standard libraries should be used first.
  • Custom validation: If you need to implement special validation logic, use [CallerArgumentExpression] instead of Expression to improve performance and gain full compiler check support.

Change Log

  • 2024-10-13 Initial version created.