C# 8.0 nullable references: non-nullable is the new default
The most ambitious new feature in C# 8.0 goes by the name of nullable references. (For information on all the new features, you could always consult my book, Programming C# 8.0, from O'Reilly.)
The aim of this new feature is to mitigate what computer scientist Tony Hoare described as his "billion dollar mistake." C#'s null
keyword (like its equivalents in many other languages) can trace its roots back to Algol W, a programming language that Hoare helped design.
In this early language (it appeared in 1966) variables that refer to instances of some particular type can be set to a special value indicating that they do not refer to anything right now, a feature that has been extremely widely copied, and which many (including Tony Hoare) now think has been an extremely costly source of programming errors ever since.
What's wrong with nullability? In a world where any reference may be null, you need to take that into account anywhere your code uses a reference, or you risk a runtime failure.
Sometimes this won't be too onerous: if you initialize a variable with a new
expression at the point where you declare it, you can know that it's not null.
But even in such a simple example there's still some cognitive load: prior to C# 8, the compiler couldn't tell you if you do anything that might change it to null. But as soon as you start connecting pieces of code together, this becomes much hard: is this property I'm reading likely to return null? Am I allowed to pass null into that method? Under what circumstances can I be confident that this method I'm calling will set this out
argument to something other than null? And even remembering to check isn't the whole story: sometimes it's not even clear what you should do in the face of a null.
Numeric types don't have this problem in C#: if you write a function that takes some numbers as input and produces a number as a result, you don't have to wonder whether the values passed in are really numbers, and whether they might not in fact be nothing.
If you call such a function you don't have to wonder whether it might return nothing instead of returning a number.
Not unless you really need that as an option, in which case you can declare parameters or results of type int?
to indicate that on this particular occasion, you do actually want it to be possible to pass or return no value at all. So for numeric types, and more generally, value types, nullability has always been something you got only if you chose to opt into it.
But for reference types, prior to C# 8.0, not only was nullability the default, you couldn't even switch it off.
In fact, for reasons of backwards compatibility, nullability remains the default even for C# 8.0, because the new language features in this area are disabled until you explicitly ask for them.
However, once you turn on this new feature, it all changes. The simplest way to enable it is to add <Nullable>enable</Nullable>
inside a <PropertyGroup>
element in your .csproj
file. (As I'll be discussing in later posts in this series, more fine-grained control is possible.
If you really need to, it's possible to configure nullable behaviour line by line. However, in our recent work to switch this feature on in all of our open source projects, we've found that enabling it one project at a time has been manageable so far.)
With C# 8.0's nullable reference features fully enabled, the default changes: all references are presumed to be non-nullable unless you say so, just like with value types. (The syntax is even the same: just as you would write int?
if you really want a numeric value to be optional, you now write string?
to indicate that you want either a reference to a string or null.)
This is a pretty significant change, and it's the main reason this new feature is off by default.
Microsoft could have designed this language feature differently: they could have had references remain nullable by default, and introduced a new syntax for indicating that you want non-nullabilty.
This might have lowered the bar to adoption, but it would have been the wrong decision in the long run, because in practice, most references in most C# code are intended never to be null. (This has certainly been endjin's experience: as we have enabled nullability in our projects, the cases where we add a ?
to revert to the old "null allowed" behaviour are very much in the minority.)
Nullability is the exception, not the rule, and that's why, when you enable this new language feature, non-nullabilty is the new default.
This is even reflected in the feature's name, "nullable references." That's a curious name, given that references have been nullable since C# 1.0. But they chose it to emphasize the fact that nullability is now something you get only if you ask for it.