C# 8.0 nullable references: NotNull
So far in this sub-series on attributes for getting better results from C# 8's nullable references feature I've described the AllowNull
and DisallowNull
attributes.
Now it's NotNull
's turn.
As with all of these attributes, this enables us to describe our intent in more detail so that the compiler can find more null-related programming errors without producing spurious warnings.
This particular post is concerned with a scenario where the successful execution of a method implies that we can now be certain that something isn't null, and we'd like the compiler to know that.
Whereas DisallowNull
made a statement about what's acceptable for a method's or property's input, NotNull
tells the compiler about how things will be once a method returns.
You might have wondered why I didn't just put that in the obvious way, by saying that NotNull
makes a statement about outputs. Well that's one way to use it, but as you'll soon see, it's not the only way.
Nullable in, non-nullable out for ref arguments
Just as a property may want to accept a null input while asserting that it will never provide a null output, the same may be true for a method with a ref
parameter.
You can annotate ref
parameters that have a nullable reference type with a NotNull
attribute to indicate that although a null input is acceptable, the compiler can presume that the variable will not be null once the method returns.
This shows how the attribute is used in the .NET class library's LazyInitializer.EnsureInitialized method:
public static T EnsureInitialized<T>([NotNull] ref T? target) where T : class
Just in case you've not come across this bit of the .NET class library before, this method enables you to delay construction of an object until you need it, a practice that can help improve an application's startup time by not attempting to run code until the application knows it's necessary.
So it is a fundamental aspect of the design of this method that the reference passed to this method (typically a reference to a field) will be null the first time it is called for that field, but that the method guarantees that the field will not be null by the time the method returns.
Revealing information about an input
So why did I used slightly convoluted language in my earlier description? There's a slightly less obvious use of this attribute. You can apply it to a non-out
, non-ref
parameter:
static void ThrowIfNull([NotNull] string? x)
{
if (x == null)
{
throw new ArgumentNullException();
}
}
This asserts that if the method returns, null analysis can safely conclude that the expression passed as an argument to the method was not null. It is valid to pass in a null argument, it's just that the method won't return if that happens—it throws an exception instead.
So this is not a statement about the method's output. It is a statement about the method's inputs, and more specifically it tells the compiler something that it can infer about the input once the method returns.
The critical point about this way of using NotNull
is that there are some methods that will sometimes choose not to return.
This attribute is particularly useful if you are in the habit of writing helper methods that validate arguments and throw exceptions if they are invalid in some way.
If you have a common helper for rejecting null arguments, applying this attribute to its input means the compiler will correctly determine that the variable passed as an argument is non-null (even if it is declared as having a nullable type) for any code that comes after you checked the argument.