.NET Tools
Essential productivity kit for .NET and game developers
Caller Argument Expressions – A Look at New Language Features in C# 10
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:
- File-Scoped Namespaces
- Caller Argument Expressions
- Global Usings
- Improvements and Optimizations for Interpolated Strings
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:
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:
On the method definition, you’ll be guarded against accidentally making it self-referential …
… from providing an incorrect parameter name …
… and from conflicts with other compiler service 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:
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:
For keeping track of touch/click debounce countdowns according to locations of their calls https://t.co/l3W8wSPpdo
— Vadim Sedov (@sidovsky_) October 20, 2021
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!