Nullable Reference Types – Addressing the Billion Dollar Mistake of Null Reference
If you have been doing C# development for some time, I am sure you are vary familiar with the infamous NullReferenceException
. I certainly saw it happen many many time. Tony Hoare, the inventor of null reference, said it was the billion dollar mistake he made (Btw, it is good talk and you should check it out).
Nullable reference types was introduced in C# 8 to solve this issue. It is awesome and you should give it a try if you are on the newer version of .NET (anything newer then .NET Core 3 or .NET standard 2.1).
What’s wrong with null?
If you are not convinced, let’s start with an example:
public void PrintUserName(User user)
{
Console.WriteLine(user.Name);
}
Spot any problem with this code? The issue is user
can be null and user.Name
will throw NullReferenceException
. What you should do is check for null before accessing the member of user
.
public void PrintUserName(User user)
{
if (user is null)
{
return;
}
Console.WriteLine(user.Name);
}
The default behavior of C# requires you, the developer, to stay on top of null checking. The first example compiles fine but it fails in run time and that is the billion dollar mistake. Fixing the issue in production is much more expansive then fixing it during development. It is better if the first example gives compile error and refuse to compile. It force you to add the null check and eliminate the NullReferenceException
we all hate to see in production.
Nullable reference types
This is what nullable reference types is trying to solve. The idea is super simple. Reference type will by default not be nullable. So User user = null;
is not valid anymore. If you want user
to be nullable, you need to do User? user = null;
. It behaves (kind of) like value type, like you can’t write int someInt = null;
.
It also has the intelligent to check if the value of a nullable reference can be null at a certain point. For example in:
public void PrintUserName(User? user)
{
if (user is null)
{
return;
}
Console.WriteLine(user.Name);
}
It can detect that user cannot be null when accessing user.Name
because we have a null check before. If the null check is missing, it will warn the developer that user.Name
can cause problem.
Nullable warnings?
You read it right. C# is only going to warn you. Unfortunately, Microsoft went a bit soft when introducing this new feature. If you turn on nullable reference type by adding <Nullable>enable</Nullable>
in .csproj
(it is on by default for new project), it actually just give you warning if you try to do something with nullable reference that may cause NullReferenceException
.
I believe at one time during C# 8 preview, it actually will error out and not compile if you have non null
safe code. However, people were not happy how this break their legacy code and think this is a bit forced (although you can turn it off and it is not on by default when you upgrade your project). So, when C# 8 release, Microsoft make this to only emit warning when nullable is on.
I want error instead
Is it all doomed? No, the fix is quite simple. You need to add <WarningsAsErrors>Nullable</WarningsAsErrors>
in .csproj
and we can treat nullable warnings as error. With this we are ready to eliminate NullReferenceException
once and for all.
Fixing errors
The first time you turn on nullable reference types and nullable warnings as error, you will see a bunch of error show up. Don’t panic. Most of them are easy to fix.
Before talking about fixes, the main takeaway is that you should explicitly think about null in your application. In general, null
is something to be avoided. Try to stick with non-nullable object and keep the use of nullable minimal. For example, if I have a method Send(string message)
, it doesn’t really make sense to send null
or nothing as message. If message in nullable when being passed, the issue is probably on the caller. The caller of the method should do a null check and do not pass null into Send()
.
One common error is when assigning null
to reference type, like User user = null;
. There are two possible fixes. First, ask yourself if user
need to be null here. If yes, do User? user = null;
. If not, check how user
is initiated and maybe move that initiation earlier, like User user = GetUser();
.
The other common error is when operating on maybe null
object, like calling user.Name
where user
is nullable. The fix here first is to add null check, like my example above. The other possible is handling null in the spot. For example, you can do user?.Name
, which return null
if user
is null
. In rare case where C# does not recognize your null check, you can do user!.Name
, which means you are sure user
is not null and just want to move on. There are cases for it like when you are chaining LINQ and some Where()
operation before already checked for null. Be careful with !
though because you will still receive NullReferenceException
if the object turns out to be null.
Another error you may see is when you pass a nullable object to a method but that method require a non-nullable object, like Send(message);
, where message
is nullable string?
but Send()
takes non-nullable string
. You can change the signature of Send()
to accept string?
, which mean you will need to handle null in Send()
. The other way is do Send(message ?? "default message")
. It passes "default message"
when message
is null.
For more on resolving nullable error, check out this Microsoft documentation.
Conclusion
Nullable reference types is a very cool C# feature and you should give it a try. It forces you to think about null
in your application, which is a good practice. Hope this post helps you and let NullReferenceException
be history.