İlkay İlknur

CallerArgumentExpression Attribute In C# 10

November 12, 2021

Caller information attributes are one of the useful ways to obtain caller information to a method in C#. C# 10 brings a new member to the caller information attributes.

Before mentioning the new member, let's see why the team had decided to implement it.

Consider we have a Exists method with multiple assertions.

bool Exists(string[] array, int index)
{
    Debug.Assert(array != null);
    Debug.Assert(index >= 0);

    return true;
}

When we call the Exists method with an invalid argument such as a null array, the Debug.Assert throws an exception, and the exception stack trace includes file path, line number, and method name.

Process terminated. Assertion failed.
   at Program.<<Main>$>g__Exists|0_0(String[] array, Int32 findIndex) in C:\Users\ilkay\source\repos\ConsoleApp34\ConsoleApp34\Program.cs:line 11
   at Program.<Main>$(String[] args) in C:\Users\ilkay\source\repos\ConsoleApp34\ConsoleApp34\Program.cs:line 7

C:\Users\ilkay\source\repos\ConsoleApp34\ConsoleApp34\bin\Debug\net6.0\ConsoleApp34.exe (process 10336) exited with code -2146232797.
Press any key to close this window . . .

As you see, the stack trace isn't very helpful, and we need to open the source code and find the related line to see which assertion has failed. However, it would be great to see the failed expression in the exception message.

In C# 10, you can use the new CallerArgumentExpression attribute and capture the expression passed to a method. Let's write a simple Assert method using the CallerArgumentExpression attribute and use it in the Exists method.

void Assert(bool condition, [CallerArgumentExpression("condition")] string expression = default)
{
    if (!condition)
    {
        Console.WriteLine($"Condition has failed. Condition={expression}");
    }
}

bool Exists(string[] array, int index)
{
    Assert(array != null);
    Assert(index >= 0);

    return true;
}

Calling the Exists method with a null array argument will produce the following output in the console.

Condition has failed. Condition=array != null

.NET 6 introduces a new null checking API(ArgumentNullException.ThrowIfNull) that uses the CallerArgumentExpression to obtain the expression passed to the method.

Source code of the ThrowIfNull method.

public class ArgumentNullException : ArgumentException
{
    /// <summary>Throws an <see cref="ArgumentNullException"/> if <paramref name="argument"/> is null.</summary>
    /// <param name="argument">The reference type argument to validate as non-null.</param>
    /// <param name="paramName">The name of the parameter with which <paramref name="argument"/> corresponds.</param>
    public static void ThrowIfNull([NotNull] object? argument, [CallerArgumentExpression("argument")] string? paramName = null)
    {
        if (argument is null)
        {
            Throw(paramName);
        }
    }

    [DoesNotReturn]
    private static void Throw(string? paramName) =>
        throw new ArgumentNullException(paramName);
}

As you see, it is so easy to obtain the expression information passed to a method in C# 10. I think testing and validation libraries will use the CallerArgumentExpression attribute to provide more meaningful assertion messages in the coming releases.

See you next time!