.NET Tools How-To's

Caller Argument Expressions – A Look at New Language Features in C# 10

ReSharper and Rider support for C# 8

Welcome to the second part of our series, where we take a closer look at the new C# language features the .NET team has implemented, and how ReSharper and Rider make it easy to adopt them in your codebase. Welcome to C# 10 and .NET 6!

In this series, we are looking at:

In this post we will discuss all relevant aspects of Caller Argument Expressions and where they’re helping. Have fun and be amazed! 😃

Caller Argument Expression

The new CallerArgumentExpressionAttribute joins the family of existing attributes to determine caller information, like the member name or file path, without using reflection. This can then be used to provide better information about the origin of an error. The new attribute allows capturing an expression passed for a parameter as a simple string:

public static void Main(string[] args)
{
    // What you call
    Assert(args.Length != 1);

    // What gets compiled
    Assert(args.Length != 1, "args.Length != 1");
}

public static void Assert(
    bool condition,
    [CallerArgumentExpression("condition")]
    string conditionExpression = default)
{
    if (!condition)
        throw new Exception($"Condition failed: {conditionExpression}");
}

This opens up great possibilities for both library and infrastructure code, including getting rid of using Expression<Func<T>> for the sole purpose of getting the name of the passed expression and the involved reflection overhead.

One of my personal favorites that benefit from the new attribute is the NotNull<T>(string message = null) extension method, which asserts an object to be not null as follows:

static void M(Person person)
{
    var addresses = person.NotNull().Addresses;
    var relatives = person.Relatives.NotNull();
}

static void NotNull<T>(this T obj, string message = null)

Previously, the best you could achieve without providing a message was to include the typeof(T).FullName in the exception message. Now you can add another parameter that captures the expression on which the method was called. With a few Roslyn nullability annotations, you can improve the assertion method as follows:

[return: NotNull]
public static T NotNull<T>(
    [NotNull] this T? obj,
    string? message = default,
    [CallerArgumentExpression("obj")]
    string? parameterName = default)
    where T : class
{
    return obj ?? throw new ArgumentNullException(parameterName, message);
}

With this method, you can flexibly assert expressions for being not null. You can let the CallerArgumentExpression attribute work its magic, or you can pass a custom message. For the latter, ReSharper and Rider will even offer code completion to use the assigned variable name instead of the invoked expression:

Code-Completion for Asserted Variable

Of course, ReSharper and Rider 2021.3 come with a set of new inspections to help you use the attribute in the correct manner. For instance, on the call site you will get a warning when a value is explicitly passed, which is most likely unintended:

Warning for Explicit Argument

On the method definition, you’ll be guarded against accidentally making it self-referential

Warning for Self-Referential Parameter

… from providing an incorrect parameter name

Warning for Invalid Parameter

… and from conflicts with other compiler service attributes:

Warning for Conflicting Attributes

An issue that remains, for now, is the inability to reference parameters with the nameof operator to avoid magic strings. Make sure to give the issue a vote if it’s important to you! Until then, ReSharper and Rider have you covered and will provide code-completion for valid parameter names but also update parameter references when you rename them:

Code-Completion for valid Parameter Names

We were curious and asked people what they’re using the caller-information attributes for. Besides exception messages, validation, and INotifyPropertyChanged variations, we’ve also found DebounceMonitoring as a very interesting project:

Download ReSharper 2021.3 EAP or check out Rider 2021.3 EAP to start taking advantage of C# 10 in the best possible way. We’d love to hear your thoughts!

Discover more