There was once a computer programmer. There had been programmers before this one and there were programmers after him. In that way he wasn’t remarkable but in many, many other ways he was. Sir Tony Hoare was the creator of the Quicksort, Hoare logic and, unfortunately, the concept of null.
Speaking at QCon in 2009 he, unfairly, took the blame for null which he called a billion dollar mistake.
I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.-Sir Tony Hoare
While null might have been a mistake in ALGOL W, the fact that this mistake has propagated into most every language developed since, falls at the feet of many, many others. I’m sure that you have run into it a myriad of times when dealing with an object that you think absolutely, cannot possibly be null is. How frequently have you torn at your hair when faced with the message
How frequently have we all?
Fortunately, C# 8 is bringing nullable reference types to the masses. For years functional programming languages have avoided null through use of the Maybe monad. F# attempts to avoid null by forcing you to explicitly state when a value can be null.
Right now C# 8 is still in beta so you need to select it explicitly in your project configuration. In Visual Studio 2019 you can go to the properties of your project, select the
Build tab and then open up
In there, select language version and update it to
C# 8.0 (beta). This gets you halfway there, you also need to enable NullableContextOptions in the project file. There is, currently, no UI integration for this flag so it must be done in the .csproj file directly.
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp2.2</TargetFramework> <LangVersion>8.0</LangVersion> <NullableContextOptions>enable</NullableContextOptions> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> <WarningsAsErrors /> </PropertyGroup> </Project>
This sample project file also contains the PropertyGroup to treat all warnings as if they were errors. This is a favorite flag of mine and something I turn on as soon as I start a new project and as soon as possible when working with an old project. The null checks currently produce warnings and not errors so turning on warnings as errors helps elevate them to critical.
If you’re updating an existing project to use NullableContextOptions then chances are you’ll start seeing compiler errors as soon as you run your next build. On a new project, we’ll have to play a little to see the errors.
Testing It Out
What does this new feature actually do? Basically, it requires that you be explicit about handling objects which can be null. We’ve sort of had this for years on primitive types like int and double. The same syntax of using
?after the type works for reference types now.
In this simple example, we see what happens when you assign null to a string type: you get an error.
In previous versions of C#, or even with NullableContextOptions turned off, this code would have compiled fine. However, it would have thrown an error at runtime. By shifting the error left we, potentially, saved ourselves a lot of time and trouble dealing with Null References down the road, in production.
In OzCode’s debugging extension, there is a feature that also attempts to shift NullReferenceExceptions left, by adding “prebugging” – essentially showing you NullRefs before they actually occur – and allowing developer to do Live Coding to fix them:
That’s nice, but nothing’s nicer than preventing the possibility of a NullReferenceException to begin with, which is what the new C# 8 functionality aims to do.
If we do need to allow that string to be null then we can change it from a
String into a
String?. This will eliminate the first error but keep the second one. This is desirable because we want to make sure that our code explicitly handles the case where the string is null.
Of course, this is a fairly tame example but as applications become more complex the nullability checks become all that more useful. Jon Skeet wrote an excellent post about his experiences moving Noda Time over to using nullable references. In it, he mentions that he did find a couple of previously undiscovered bugs.
Nullable references don’t remove null from the language but they do put you in the driver’s seat to ensure that your code handles the possibility of null correctly. With apologies to Monty Python, nobody expects null.
There are a number of really interesting rules and corner cases with a change to the language this large. They’ve been pretty thoroughly explored by the C# language team.
It doesn’t look like C# 8 is going to come to the full .NET framework. At the time of writing, you’ll need to use .NET Core to gain all the advantages of C# 8.0. This is likely okay because .NET Core 3.0 is getting closer and closer to being fully compatible with the old Full Framework. C# 8 features should give you another reason to update.
Updating the BCL
One of the best parts about being a .NET developer is making use of the Base Class Library – everything in the System.* namespace. Wouldn’t it be nice if we could gain some information about whether these methods can handle null or will return null?
As you might imagine that is a herculean task but investigation into how it can be done has already started. Hopefully, some of the existing code contracts can be reused.
Null references have been the bane of our existence for many a year. C# 8 with nullable reference types should help correct some of the gaps in code quality we’ve been facing. This is yet another example of C# stealing brilliant ideas from F# and other functional languages. I hope this practice continues as I can’t wait for C# to support discriminated unions.
I’d strongly encourage you to try this feature out and, when the time comes, make use of it on all your projects. If it only save you getting up at 3 am to fix a production bug once it is worthwhile.