void M<T>(T? t) where T: notnull
This would mean that the parameter is the nullable version of T
, and T
is constrained to be notnull
. If T
were a string
, then the actual signature of M
would be M<string>([NullableAttribute] T t)
, but if T
were an int
, then M
would be M<int>(Nullable<int> t)
. These two signatures are fundamentally different, and this difference is not reconcilable.
Because of this issue between the concrete representations of nullable reference types and nullable value types, any use of T?
must also require you to constrain the T
to be either class
or struct
.
Finally, the existence of a T?
that worked for both nullable reference types and nullable value types does not address every issue with generics. You may want to allow for nullable types in a single direction (i.e., as only an input or only an output) and that is not expressible with either notnull
nor a T
and T?
split unless you artificially add separate generic types for inputs and outputs.
Nullable preconditions: AllowNull
and DisallowNull
Consider the following example:
This might have been an API that we supported prior to C# 8.0. However, the meaning of string
now means non-nullable string
! We may wish to actually still allow null
values, but always give back some string
value with the get
. Here’s where AllowNull
can come in and let you get fancy:
Since we always make sure that we get no null
value with the getter, I’d like the type to remain string
. But we want to still accept null
values for backwards compatibility. The AllowNull
attribute lets you specify that the setter accepts null
values. Callers are then affected as you’d expect:
Note: there is currently a bug where assignment of null
conflicts with nullable analysis. This will be addressed in a future update of the compiler.
Consider another API:
In this case, MyHandle
refers to some handle to a resource. Typical use for this API is that we have a non-null
instance that we pass by reference, but when it is cleared, the reference is null
. We can get fancy and represent this with DisallowNull
:
This will affect any caller by emitting a warning if they pass null
, but will warn if you attempt to "dot" into the handle
after the method is called:
These two attributes allow us single-direction nullability or non-nullability for those cases where we need them.
More formally:
The AllowNull
attribute allows callers to pass null
even if the type doesn’t allow it. The DisallowNull
attribute disallows callers to pass null
even if the type allows it. They can be specified on anything that takes input:
Value parameters
in
parameters
ref
parameters
fields
properties
indexers
Important: These attributes only affect nullable analysis for the callers of methods that are annotated with them. The bodies of annotated methods and things like interface implementation do not respect these attributes. We may add support for that in the future.
Nullable postconditions: MaybeNull
and NotNull
Consider the following example API:
Here we have another problem. We’d like Find
to give back default
if nothing is found, which is null
for reference types. We’d like Resize
to accept a possibly null
input, but we want to ensure that after Resize
is called, the array
value passed by reference is always non-null
. Again, applying the notnull
constraint doesn’t solve this. Uh-oh!
Enter [MaybeNull]
and [NotNull]
. Now we can get fancy with the nullability of the outputs! We can modify the example as such:
And these can now affect call sites:
The first method specifies that the T
that is returned could be a null
value. This means that callers of this method must check for null
when using its result.
The second method has a trickier signature: [NotNull] ref T[]? array
. This means that array
could be null
as an input, but when Resize
is called, array
will not be null
. This means that if you "dot" into array
after calling Resize
, you will not get a warning. But after Resize
is called, array
will no longer be null
.
More formally:
The MaybeNull
attribute allows for a return type to be null
, even if its type doesn’t allow it. The NotNull
attribute disallows null
results even if the type allows it. They can be specified on anything that produces output:
Method returns
out
parameters (after a method is called)
ref
parameters (after a method is called)
fields
properties
indexers
Important: These attributes only affect nullable analysis for the callers of methods that are annotated with them. The bodies of annotated methods and things like interface implementation do not respect these attributes. We may add support for that in the future.
Conditional postconditions: MaybeNullWhen(bool)
and NotNullWhen(bool)
Consider the following example:
Methods like this are everywhere in .NET, where the return value of true
or false
corresponds to the nullability (or possible nullability) of a parameter. The MyQueue
case is also a bit special, since it’s generic. TryDequeue
should give a null
for result
if the result is false
, but only if T
is a reference type. If T
is a struct, then it won’t be null
.
So, we want to do three things:
Signal that if IsNullOrEmpty
returns false
, then value
is non-null
Signal that if TryParse
returns true
, then version
is non-null
Signal that if TryDequeue
returns false
, then result
could be null
, provided it’s a reference type
Unfortunately, the C# compiler does not associate the return value of a method with the nullability of one of its parameters! Uh-oh!
Enter NotNullWhen(bool)
and MaybeNullWhen(bool)
. Now we can get even fancier with parameters:
And these can now affect call sites:
This enables callers to work with APIs using the same patterns that they’ve used before, without any spurious warnings from the compiler:
If IsNullOrEmpty
is true, it’s safe to "dot" into value
If TryParse
is true, then version
was parsed and is safe to "dot" into
If TryDequeue
is false, then result
might be null
and a check is needed (example: returning false
when the type is a struct is non-null
, but false
for a reference type means it could be null
)
More formally:
The NotNullWhen(bool)
signifies that a parameter is not null even if the type allows it, conditional on the bool
returned value of the method. The MaybeNullWhen(bool)
signifies that a parameter could be null even if the type disallows it, conditional on the bool
returned value of the method. They can be specified on any parameter type.
Consider the following example:
In this case, we’d like to return a possibly null
string, and we should also be able to accept a null
value as input. So the signature accomplishes what I’d like to express.
However, if path
is not null
, we’d like to ensure that we always give back a string. That is, we want the return value of GetFileName
to be non-null, conditional on the nullness of path
. There’s no way to express this as-is. Uh-oh!
Enter NotNullIfNotNull(string)
. This attribute can make your code the fanciest, so use it with care! Here’s how we’ll use it in my API:
And this can now affect call sites:
More formally:
The NotNullIfNotNull(string)
attribute signifies that any output value is non-null
conditional on the nullability of a given parameter whose name is specified. They can be specified on the following constructs:
Method returns
ref
parameters
Flow attributes: DoesNotReturn
and DoesNotReturnIf(bool)
You may work with multiple methods that affect control flow of your program. For example, an exception helper method that will throw an exception if called, or an assertion method that will throw an exception if an input is true
or false
.
You may wish to do something like assert that a value is non-null, and we think you’d also like it if the compiler could understand that.
Enter DoesNotReturn
and DoesNotReturnIf(bool)
. Here’s an example of how you could use either:
When ThrowArgumentNullException
is called in a method, it throws an exception. The DoesNotReturn
it is annotated with will signal to the compiler that no nullable analysis needs to happen after that point, since that code would be unreachable.
When MyAssert
is called and the condition passed to it is false
, it throws an exception. The DoesNotReturnIf(false)
that annotates the condition
parameter lets the compiler know that program flow will not continue if that condition is false. This is helpful if you want to assert the nullability of a value. In the code path following MyAssert(value != null);
the compiler can assume value
is not null.
DoesNotReturn
can be used on methods. DoesNotReturnIf(bool)
can be used on input parameters.
Evolving your annotations
Once you annotate a public API, you’ll want to consider the fact that updating an API can have downstream effects:
Adding nullable annotations where there weren’t any may introduce warnings to user code
Removing nullable annotations can also introduce warnings (e.g., interface implementation)
Nullable annotations are an integral part of your public API. Adding or removing annotations introduce new warnings. We recommend starting with a preview release where you solicit feedback, with aims to not change any annotations after a full release. This isn’t always going to be possible, but we recommend it nonetheless.
Current status of Microsoft frameworks and libraries
Because Nullable Reference Types are so new, the large majority of Microsoft-authored C# frameworks and libraries have not yet been appropriately annotated.
That said, the "Core Lib" part of .NET Core, which represents about ~20% of the .NET Core shared framework, has been fully updated. It includes namespaces like System
, System.IO
, and System.Collections.Generic
. We’re looking for feedback on our decisions so that we can make appropriate tweaks as soon as possible, and before their usage becomes widespread.
Although there is still ~80% CoreFX to still annotate, the most-used APIs are fully annotated.
Roadmap for Nullable Reference Types
Currently, we view the full Nullable Reference Types experience as being in preview. It’s stable, but the feature involves spreading nullable annotations throughout our own technologies and the greater .NET ecosystem. This will take some time to complete.
That said, we’re encouraging library authors to start annotating their libraries now. The feature will only get better as more libraries adopt nullability, helping .NET become a more null
-safe place.
Over the coming year or so, we’re going to continue to improve the feature and spread its use throughout Microsoft frameworks and libraries.
For the language, especially compiler analysis, we’ll be making numerous enhancements so that we can minimize your need to do things like use the null-forgiveness (!
) operator. Many of these enhancements are already tracked on the Roslyn repo.
For CoreFX, we’ll be annotating the remaining ~80% of APIs and making appropriate tweaks based on feedback.
For ASP.NET Core and Entity Framework, we’ll be annotating public APIs once some new additions to CoreFX and the compiler are added.
We haven’t yet planned how to annotate WinForms and WPF APIs, but we’d love to hear your feedback on what kinds of things matter!
Finally, we’re going to continue enhancing C# tooling in Visual Studio. We have multiple ideas for features to help using the feature, but we’d love your input as well!
Next steps
If you’re still reading and haven’t tried out the feature in your code, especially your library code, give it a try and please give us feedback on anything you feel ought to be different. The journey to make unanticipated NullReferenceException
s in .NET go away will be lengthy, but we hope that in the long run, developers simply won’t have to worry about getting bitten by implicit null
values anymore. You can help us. Try out the feature and begin annotating your libraries. Feedback on your experience will help shorten that journey.
Cheers, and happy hacking!
Author
Phillip is a PM on the .NET team, focusing on the F# language, F# documentation, F# tooling, and project system tooling. He wishes he had more time to code, but that doesn't stop him from having fun with people on GitHub. He loves functional programming and language-related tooling, and is always available to chat about wild and wacky ways to make programming more enjoyable.
“Nullable reference types” is an idea that is either redundant or paradoxical. It should always be possible for a reference type to be null, and I have no interest in explicitly telling the compiler ahead of time when or if that will be the case.
That’s why it took until C# 8.0 to introduce this ridiculous idea. People have too much time on their hands now. They’re bored. C# has gone null-crazy in the last few versions: null-coalescing ??, null-forgiving !, … It’s all nonsense.
C# was supposed to be a simple language that let you write code fast. All this time spent worrying about null values is really slowing me down. I plan to avoid it in my own code whenever possible. If there’s a way to avoid these new features and write good old C# 3 code, believe me I will.
I disagree with your premise that any reference type should be allowed to be null. In fact, I think it should be (and probably would be if they could start all over again with C) the other way around. You should have to specify when something can be nullable, especially since most of the time reference types are used in such a way that null has no meaning other than being a hindrance and source of runtime errors.
I do agree that this implementation is horrendously complex and still doesn’t provide guarantees. That is why I still prefer the usage of a proper “maybe” type (like F# has) and wish that instead of this (or at least in addition) Microsoft would just add such a type to the BCL.
Is there any way to decorate a method that will assert something, but the check is inside the assertion method?
Like:
public static void ValidateProvider(string? provider)
if (string.IsNullOrWhiteSpace(provider))
throw new ArgumentException("The provider is required.", nameof(provider));
Do we have any current tooling to indicate whether the returned value obtained from await
-ing an async
method may be null
, or will not be null
?
That is, for a method:
public async Task<T> FooAsync<T>(/* some params */)
// Do some async work …
return tResult; // `tResult` may be `null`
which the user executes with:
var t = await ExecuteAsync(/* etc */);
… do we currently have a way to attribute code to indicate that t
may be null (so as to generate user warnings if unguardedly dereferenced)? Or indeed that t
will not be null and can be safely dereferenced?
I am imagining being able to attribute these methods something like [asyncreturn: MaybeNull]
or [awaitreturn: MaybeNull]
.
Or if the existing syntax [return: MaybeNull]
on an async
method actually already provides this for the return value t
from await
-ing the method, it would be worth documenting that. (I am not expecting it does; I can see reasons why that could be both a good and bad idea.)
Note: For my purposes T
here is an unconstrained generic: it may be a reference type or value type, and may be nullable or non-nullable. (Library code often has public APIs of this form.)
Hey Dylan,
The answer to this question is, “it depends” 🙂
t’s all about the types in the end. It’s certainly possible to write code that subverts the type system, or you may have a complicated pattern where nulls are only allowed in or out (so-called “one-way nullability”), but not in both cases. In those scenarios, careful applications of the attributes I document above may be necessary.
However, you may not need them at all. Consider the following method (and local function):
static async Task M()
// Correctly handles non-null
var x = await IdAsync("hello");
Console.WriteLine(x.Length);
// Correctly handles null
var y = await IdAsync<string?>(null);
Console.WriteLine(y.Length); // Warning
static async Task<T> IdAsync<T>(T t) => await Task.FromResult(t);
The generic async method below uses unconstrained generics, so that means what I get back depends on what I pass in.
In the first case, since I pass in a string
(that is, a non-nullable string
), I get a non-nullable string
back as the type, since the T
is inferred to be string
, which is non-nullable.
In the second case, I ass in a null
and need to parameterize the type to be string?
since it can’t infer a signature unless I do that. Since T
is now string?
, I get a warning if I try to “dot” into what I get back. Additionally, starting with the VS 16.4 release, we’ll add a tooltip that says that y
could be null
at a particular location before a check is put into place.
So I think this is going to come down to what you want to guarantee as a library author. It sounds like you cannot guarantee that the result will be non-null, nor can you assume that inputs will be non-null, so perhaps no attributes will be sufficient for your users.
@Phillip
So how does this jive with async enumerable? What if you have something like:
public static async Task<TResult> LastOrDefaultAsync(this IAsyncEnumerable<TResult> enumerable)
TResult last = default!;
await foreach (var item in enumerable)
last = item;
return last;
How do I state to callers that the return value may be null in order to shut up the compiler? In a non-async method I would just use [return: MaybeNull]. How do I do that here?
With the nullable reference types, has there been any discussion in regards to uniting nullable reference & value types? I can only begin to imagine how much work would have to be put in, and I’m not sure to what extent it’s even realistic, however, with the new approach of handling reference types, generics etc. it is gonna be pain down the road.
Now that this has released and I gave it a try, it seems to me that the design has a major flaw that is preventing me from using it. You support the static analysis on all projects regardless of the target framework. But the attributes are only available in .net Standard 2.1. Most of my class library projects cannot be updated to .net Standard 2.1 because they are consumed by many different projects. This means I cannot use this awesome feature on these class library projects.
As mentioned above, you can copy the (very simple) source for the attributes into your own projects and the compiler will respect them, even if you’re targeting earlier versions of .NET Standard.
There’s precedent for this approach – it’s what we had to do when [CallerMember] was introduced, for example.
Hey Philip,
thank you for the prompt answer. I have to admit that I haven’t tried recent VS 2019 builds, so I can’t say now for sure if my concern is still valid. In this article you still recommend to edit the project file that to enable per project Nullable Reference Types. This would probably translate to a regular checkbox in the Project Preferences UI, which wasn’t present back then when I first tried VS2019. Have this UI support been added or people are still supposed to edit the file manually to enable the feature?
Because of the feature, the confidence that something “can never be null here” will rise.
So developers will be tempted to remove or not add a null check (if (thing != null), ArgumentNullException etc.).
1. Library authors might be tempted to remove all null checks for non-nullable types in internal code and just guard agains null at the public API level.
2. Application developers might be tempted to not guard against null because an API clearly states that its output is never intended to be null.
So what would be your guidance on how to deal with null guards in this new world?
I think it would be helpful to add some guidance about this to the documentation. I have raised an issue here: https://github.com/dotnet/docs/issues/14491
Generally I don’t think we’re quite in the business of developing strong guidance just yet, especially since the feature is still opt-in and has not percolated throughout the .NET ecosystem. We have developed guidance for CoreFX developers and I believe that much of this will also hold for many .NET library authors. However, the way we consider development in CoreFX is generally far different than app developers and some library developers. I imagine we’ll have more written guidance after the feature has been more broadly adopted outside of Microsoft and we’ve gotten enough feedback about how it “feels” from people to start collaborating on official guidance.
public IEnumerable Test(IEnumerable strs)
return strs.Where(x => !string.IsNullOrEmpty(x));
After compiling I get such error: [CS8619] Nullability of reference types in value of type ‘IEnumerable<string?>’ doesn’t match target type ‘IEnumerable<string>’
How can I fix this?
You have both “Opt in a project, opt out files” and “Opt in files one at a time” specified as <Nullable>enable</Nullable> which for me starts showing warnings for every file. After setting <LangVersion>8.0</LangVersion>, I had to use <Nullable>disable</Nullable> and #nullable enable at the top of files to get it to work one file at a time.