C# 8.0 introduced a major new feature: nullable reference types. In this post, I want to answer the question if that’s the end of the pesky NullReferenceException.

Pre C# 8.0: How to Deal with Null

Before C# 8.0 there were basically two ways of dealing with null. A defensive approach and one that requires discipline.

Null Checks

If you can’t be sure if something is null, there’s only one responsible thing to do: check if it’s null (a.k.a. being defensive). There’s just one problem with the C# type system, you can never be sure. So, as a professional programmer you will write code that looks something like this:

void ExecuteWorkflow()
    var resultStep1 = DoStep1();
    if (resultStep1 != null)
        var resultStep2 = DoStep2(resultStep1);
        if (resultStep2 != null)
            //handle resultStep2 is null
        //handle resultStep1 is null

Or like this:

void DoImportantWork(Component component, Data data)
    if (component is null) throw new ArgumentNullException(nameof(component));
    if (data is null) throw new ArgumentNullException(nameof(data));

    //do important work

Not particularly pretty. Those null checks get sprinkled all over your code base - causing a lot of noise.

Convention and Discipline

You can get around some null checks by applying a simple convention: Don’t return null. Don’t pass null. Easy, right? Well … it can work in areas where you’ve got full control over the executed code and when every programmer on the team is on the same page. However, in a reasonably sized business application you’ll eventually have to use some third-party frameworks. Do these frameworks return null? Once again, if you can’t be sure you have to do null checks.

There’s another catch to that approach. What do you do when you don’t have anything meaningful to return? You can’t return null, that violates the convention.
Throw an exception? At least you are trading a cryptic NullReferenceException for a more specific exception. That’s some improvement. But you can’t force the caller of the method to handle your exception (there’s no such thing as checked exceptions in C#).
Return a result code and have an additional out parameter? Again, that’s some improvement over returning null. But I wouldn’t call it pretty. And you can’t ensure that the caller of the method only dereferences the out parameter in the case of success.
Return some special case empty object? That’s the preferable approach. In the case of something like a list it’s straightforward to do, return an empty list instead of null. But it’s not always that easy to construct a special case empty object for an arbitrary type. (Or is it? We’ll talk about that in an upcoming post again.)

C# 8.0 to The Rescue

Null checks and conventions both have one major issue - they are error prone. It’s way too easy to make a mistake. Wouldn’t it be nice if the compiler made it impossible to mess up? That’s what C# 8.0 nullable reference types are for. If you’re looking for an introduction check out the presentation from Mads Torgerson. In this post I’ll focus on the things you have to watch out for with the new feature.

Basically, with nullable reference types enabled reference types aren’t allowed to be null. If you want that a reference type can be null, you have to add a question mark to the type definition:

string a = "can't be null";
string? b = null; //can be null

So, as long as you are dealing with non-nullable reference types, there’s no need for null checks anymore. But what if you want to include null because there is no meaningful value to assign? Back to null checks? Yes, but the compiler makes sure you don’t miss a null check. If you dereference a variable without checking it first, you get a compiler warning. That’s awesome! But there’s something you need to be aware of …

… It’s Not a Silver Bullet

To be clear, I love the new feature! I see a big potential to improve overall code quality and I want to say thank you to everyone who’s been involved. Nevertheless, I’d like to talk about some things you should keep in mind.

You only get warnings: If you do something the compiler deems unsafe, you get a compile-time warning, not an error. You can still build and execute your code (and enjoy your NullReferenceExceptions). I absolutely understand why they decided to generate compiler warnings instead of errors - enabling nullable reference types for an existing code base would else be virtually impossible. But we all know the problem with warnings, they are way too easy to ignore. The question is, how many warnings do you get when you build your solution? Zero? That’s great! You’ll notice every new warning and I’m sure you’ll get rid of it as soon as possible. But what if your build already produces hundreds of even thousands of warnings? I’ve seen that (I need to stress that that’s not how it’s supposed to be). In such a scenario warnings have little impact (yes, the squiggly lines while coding are still a great improvement).
For green field projects, you should consider enabling the TreatWarningsAsErrors compiler flag. If that’s not an option, you should think about treating some (or all) of the nullable reference types related warnings as errors. There’s a blog post by Jiří Činčura talking about that.

You can’t stop callers from passing null: That’s a result of the fact that you get only a warning when you are passing null where you shouldn’t. Now, what does that mean? Back to null checks? Yes and no. For public APIs, I’d still add null checks. For internal code, however, I’d recommend going without null checks. Unfortunately, I don’t have enough real-life experience to make a conclusive suggestion yet. What do you think about it? Is that the way to go? Tell us in the comments.

APIs need to be updated: It’s a bit like when the async await pattern was introduced. In order to tap the full potential of the new feature, everyone has to get on board. It’ll take some time for all the frameworks to get updated. Until that, you have to be cautious.
Here’s a simple code snippet that compiles without warnings but throws a NullReferenceException at runtime.

var companies = new[]
    new Company { Name = "Microsoft", Founder = "Bill Gates" },
    new Company { Name = "Apple", Founder = "Steve Jobs" }

var amazon = companies.FirstOrDefault(_ => _.Founder == "Jeff Bezos");

Be More Expressive

Real quick, there’s something else I love about the new feature. You can finally express what’s mandatory and what’s optional. Have a look at the following interface:

interface ICustomer
    string Name { get; }
    string? Tag { get; }

You immediately see that the name is mandatory, but the tag is optional. That’s fantastic for doing domain modeling! But that’s for another post.

What do you think about the shiny new nullable reference types feature? Is it a step in the right direction? Will we get rid of most NullReferenceExceptions? Or is it a source of confusion, adding to much complexity to the language? Share your thoughts in the comments.