C# 8: nullable reference types
Nullable reference types will change the code we write today.
C# 8.0 Series
Want to read my other posts about C# 8?
Prerequisites & Setup
To play with this feature you need Visual Studio 2019 and .NET Core 3.0 SDK. For existing code, this feature must be opt-in for making reference types non-nullable. And this for a good reason: it would break a lot of code because by default every reference type is assumed to be non-nullable (never null). As soon C# 8.0 is stable, the feature will be enabled by default for new projects.
We need to modify the .csproj file to enable it:
Let’s try it out
C# has two kind of types: value types (e.g. int, byte, structs) and reference types (e.g. class, interface). Reference types can be null by design and this introduces additional complexity while writing code. Especially when the project is fairly large and complex.
Imagine, we have a CarWashSalon class with a method Wash that doesn’t accept null as input parameter:
Looks quite common, right? There isn’t a way to express (at compile time) the intension of our design - car isn’t expected to be null. Calling the method with a null reference will end up with an ArgumentNullException at runtime (or even worse with a NullReferenceException…):
Wouldn’t it be awesome to be able express our design decision and get warnings (or errors) when it is not respected? This is exactly the purpose of the nullable reference types feature. The compiler (roslyn) analyzes the code and instantly tells us about the null assignment issue:
- We can’t assign null to the myCar variable because it’s non-nullable.
- We can’t pass null (in this case myCar) to void CarWashSalon.Wash(Car car) because it expects a non-nullable.
Everything is fine when we assign an instance of a Car to myCar:
But wait! What if myCar could be null (be a nullable reference type) by design? The new nullable reference type operator is solved elegantly - it’s the same as for nullable values types. By suffixing Car with a ? it becomes a nullable reference type:
Guess what happen? An error is back on our error list - CarWashSalon.Wash() still doesn’t accept null.
The compiler does control-flow analysis and detect when we try to dereferencing null. To solve the issue we could use a if statement to ensure null is not passed into the Wash method:
Otherwise, when we guarantee that myCar is never null the error is gone as well - even myCar is still nullable:
Under some conditions we would like to tell the compiler, that we know what we are doing and we are aware of the null. This situation can be expressed with the null-forgiving operator !:
As an alternative we could change the nullable context to be disabled for a specific code block:
When you are curious as myself, then you wonder how is a nullable reference type represented when the code is compiled into an assembly. There is no added type to the type system, but somehow the tooling is able to distinguish between nullable- and non-nullable reference types. A closer look with IL-Spy reveals the secret:
There is a NullableAttribute applied to the members with value 1 (non-nullable) and 2 (nullable). To avoid external dependencies, the attribute is embedded into the assembly dynamically:
In my opinion this is the most impactful feature of C# 8.0. It improves code quality and could avoid the famous NullReferenceException in most scenarios.