Thoughts on C# 8 nullable references

Recently I started working on a new C# project and was introduced by a colleague to C# 8’s new nullable references feature.

At first this statement might sound odd (it did to me), reference types within C# have always been nullable, right?

// Tada, a null string!
string myNullString = null;

However, the key behind the new feature is not to enable reference types to be nullable, but to make your code more explicit about which reference type variables can be null.

In fact, that little snippet above actually provides the following compiler warning when using nullable references:

Converting null literal or possible null value to non-nullable type.

So what do I do?

In order to get rid of the new compiler warning we now have to use the ? operator after the type name, this marks the variable as being nullable.

// Tada, an explicitly nullable string!
string? myNullString = null;

But it does a lot more than that!

Now code that makes use of that variable will produce compiler warnings if they don’t check the variable for null, or try to pass it to a function that states it wants a non nullable string type

For example lets look at this fascinating function:

1
2
3
4
public static string[] SplitString(string str)
{
    return str.Split(",");
}

In normal C# the following function can be called with either a valid or null string, it’s up to the implementation to decide how it handles null input and it’s up to the user to decide if they’re going to make sure they pass a valid string.

If we now head back to the world of nullable references with this code:

1
2
string myNullString = null;
SplitString(myNullString);
We get the following compiler warning:

Possible null reference argument for parameter ‘str’ in ‘project.SplitString’.

A nice, clear warning that SplitString has explicitly indicated that it wants a valid string, If we want to update SplitString to state that it’s happy to deal with null strings too, then we simply change parameter str type to string?

Why?

I’ve personally seen the following benefits of nullable references in my new project:

More explicit intent

It allows you to be clear about the expectations of your code, you either expect input of a nullable type or you don’t. Same goes for return types, you can now be much clearer that a function may return null in certain circumstances.

The expectations are baked into the types of our interface/api rather than in the documentation.

For example:

1
2
3
4
public class Database
{
    Item? GetItemById(int id);
}
The fact that GetItemById can return null is clear, if we weren’t using nullables, users would have to look at the documentation for the function to work out if they needed to handle null results and the compiler wouldn’t be able to provide them with warnings.

It forces you to handle nullable paths

Due to the compilers code analysis, if you have warnings as errors (like we do); the new feature forces you to consider your dataflow better.

1
2
var item = Database.GetItemById(123);
SplitString(item.Name);

Now we’re warned about our usage we must decide how we want to deal with conditions:

Dereference of a possibly null reference.

Enforces types to be valid upon construction

Due to the fact that all non nullable reference types must be initialized, otherwise they’ll default to null. Classes must provide non null default values for their members, either via constructors (my preferred method) or by default values at the member level

1
2
3
4
public class Item
{
    public string Name { get; }
}

If we don’t, the compiler produces the following:

Non-nullable property ‘Name’ is uninitialized. Consider declaring the property as nullable.

If we’re really happy with an item optionally having a name then we can change it to string? otherwise, we must make sure that an Item has a defined non null Name upon construction

1
2
3
4
5
6
7
8
public class Item
{
    public Item(string name)
    {
        Name = name;
    }
    public string Name { get; }
}

I’ve found that using the constructor to fix this warning has created (in my project) a move closer towards types being immutable after creation and made it clearer where a type’s properties and fields are mutated.

Gotchas

I’m a pretty big fan of nullable references, I can’t imagine I’ll start a new C# project without it from now on. But it did have a few points that confused me at the start.

Hopefully they’ll help clear things up for you too 😊

It’s entirely syntactic sugar

None of the above examples will stop a non nullable type from being null, this is a feature that enables better warnings at compilation time.

From the Microsoft documentation:

There’s no runtime difference between a non-nullable reference type and a nullable reference type. The compiler doesn’t add any runtime checking for non-nullable reference types.

It’s not about removing nulls, it’s about declaring intent

As mentioned in this Microsoft documentation, it’s best not to think of this feature as a means of removing null from your code. It’s better to think of it as an addition to your toolbox for creating higher quality code with clearer intent.

Don’t use this feature to remove all null values from your code. Rather, you should declare your intent to the compiler and other developers that read your code

WarningsAsErrors project setting doesn’t work

We found that enabling the <WarningsAsErrors>true</WarningsAsErrors> setting in our C# project file still didn’t flag the nullable warnings as errors and we had to add them manually, here’s what we have in our projects.

1
2
3
4
<LangVersion>8</LangVersion>
<Nullable>enable</Nullable>
<WarningsAsErrors>true</WarningsAsErrors>
<WarningsAsErrors>CS8600;CS8602;CS8603;CS8601;CS8618;CS8604</WarningsAsErrors>

Conclusion

Modern applications rely on so much state and data flow between systems and external libraries that any additional intent that we can bake into our code is, in my mind a solid move forward towards improving code quality. Especially when it’s combined with first class analysis built into the complier.