C# 8.0 nullable references: embrace the expressiveness
In my previous post in this series on C# 8.0 nullable references, "non-nullable is the new default" I talked about some technical details of the change to the language.
In this post I'm going to talk about one of the benefits this new language feature has to offer.
As well as the obvious win—the ability to detect null-related bugs in your code at compile time—enabling nullable references offers a more subtle benefit: it can improve the expressiveness of your code. This will help anyone reading your code to understand it better.
And for types that represent entities in your application domain, this expressivity may enable you to represent domain model constraints slightly better. Successful code often ends up being read many, many times after being written, so communicative code is the gift that keeps on giving.
When you compile C# with nullable references enabled, the code always states clearly for any variable, parameter, field, or property whether null is an acceptable value.
For each relevant declaration you need to decide whether, say,
string is accurate—this is a value that should not be null—or whether there is something about either the logic of the code or perhaps the application domain that means
string? is appropriate.
When writing new code, this shouldn't entail significant extra effort: you should know whether you expect null to be a possibility, because you can't write correct code without knowing that. So this information has always been in your head when writing new code, but now you get to record it in the source.
This means that whenever someone next looks at the code they won't need to try to work out what was in your head when you wrote it. (This can be hard enough even if you're looking at your own code from a few months ago.) You have recorded clearly what you had in mind in a way that is visible to anyone reading the code, as well as being visible to the compiler.
Strictly speaking, you don't have to enable nullability fully to enjoy this benefit. It is sufficient for the code to be in an enabled nullable annotation context. What's one of those, you might ask?
The nullable reference feature has been designed so that we can enable it gradually.
You don't have to turn it on for the entire project if you don't want to—you can use the
#nullable directive to control it for a single file, or even line by line by using the directive multiple times in a file.
You can control two aspects of nullable support independently: you can turn on the warnings without having to state whether your own declared variables, properties, etc. are nullable. You can either add
#nullable enable warnings to a source file, or put
<Nullable>warnings</Nullable> in your
In this case, all references in your own code will be treated as null-oblivious, just like how the compiler treats references defined in external libraries built without nullable annotations. Conversely, you can enable just the ability to express nullability without having to see the resulting warnings with
#nullable enable annotations or, in the project file,
The terminology Microsoft uses is a little odd, but consistent and well defined. Any line of C# code is in two contexts: a nullable warning context and a nullable annotation context. And each of these contexts can be either enabled or disabled.
So if you just switch the feature on completely for a whole project, all your code is in an enabled nullable warning context, and an enabled nullable annotation context.
If you decide you just want the warnings but you don't want to be required to state whether your fields, variables, etc. are nullable, then you can choose to use an enabled nullable warning context, and a disabled nullable annotation context. The setting in the project file determines the default enabled/disabled state for both contexts, and then the
#nullable directive can override the setting for either context.
Start expressing yourself
The practical upshot is that even if you're not yet ready to deal with the warnings that show up when you fully enable nullable references, you can still take advantage of the ability to express yourself more precisely today.
When writing new code, you already know whether you mean for particular things to be nullable.
You could write new code in an enabled nullable annotation context, so that you can express that knowledge in the code, even if the rest of your project is not using this new feature at all.