Skip to content
Ian Griffiths By Ian Griffiths Technical Fellow I
C# 8.0 nullable references: embrace the expressiveness

In my previous post in this series on C# 8.0 nullable references, "non-nullable is the new default" I talked about some technical details of the change to the language.

In this post I'm going to talk about one of the benefits this new language feature has to offer.

As well as the obvious win—the ability to detect null-related bugs in your code at compile time—enabling nullable references offers a more subtle benefit: it can improve the expressiveness of your code. This will help anyone reading your code to understand it better.

And for types that represent entities in your application domain, this expressivity may enable you to represent domain model constraints slightly better. Successful code often ends up being read many, many times after being written, so communicative code is the gift that keeps on giving.

The Introduction to Rx.NET (v6.1) 3rd Edition (2025) Book, by Ian Griffiths & Lee Campbell, is now available to download for FREE.

When you compile C# with nullable references enabled, the code always states clearly for any variable, parameter, field, or property whether null is an acceptable value.

For each relevant declaration you need to decide whether, say, string is accurate—this is a value that should not be null—or whether there is something about either the logic of the code or perhaps the application domain that means string? is appropriate.

When writing new code, this shouldn't entail significant extra effort: you should know whether you expect null to be a possibility, because you can't write correct code without knowing that. So this information has always been in your head when writing new code, but now you get to record it in the source.

This means that whenever someone next looks at the code they won't need to try to work out what was in your head when you wrote it. (This can be hard enough even if you're looking at your own code from a few months ago.) You have recorded clearly what you had in mind in a way that is visible to anyone reading the code, as well as being visible to the compiler.

Nullable contexts

Strictly speaking, you don't have to enable nullability fully to enjoy this benefit. It is sufficient for the code to be in an enabled nullable annotation context. What's one of those, you might ask?

The nullable reference feature has been designed so that we can enable it gradually.

You don't have to turn it on for the entire project if you don't want to—you can use the #nullable directive to control it for a single file, or even line by line by using the directive multiple times in a file.

Programming C# 12 Book, by Ian Griffiths, published by O'Reilly Media, is now available to buy.

You can control two aspects of nullable support independently: you can turn on the warnings without having to state whether your own declared variables, properties, etc. are nullable. You can either add #nullable enable warnings to a source file, or put <Nullable>warnings</Nullable> in your .csproj.

In this case, all references in your own code will be treated as null-oblivious, just like how the compiler treats references defined in external libraries built without nullable annotations. Conversely, you can enable just the ability to express nullability without having to see the resulting warnings with #nullable enable annotations or, in the project file, <Nullable>annotations</Nullable>.

The terminology Microsoft uses is a little odd, but consistent and well defined. Any line of C# code is in two contexts: a nullable warning context and a nullable annotation context. And each of these contexts can be either enabled or disabled.

So if you just switch the feature on completely for a whole project, all your code is in an enabled nullable warning context, and an enabled nullable annotation context.

If you decide you just want the warnings but you don't want to be required to state whether your fields, variables, etc. are nullable, then you can choose to use an enabled nullable warning context, and a disabled nullable annotation context. The setting in the project file determines the default enabled/disabled state for both contexts, and then the #nullable directive can override the setting for either context.

Start expressing yourself

The practical upshot is that even if you're not yet ready to deal with the warnings that show up when you fully enable nullable references, you can still take advantage of the ability to express yourself more precisely today.

When writing new code, you already know whether you mean for particular things to be nullable.

You could write new code in an enabled nullable annotation context, so that you can express that knowledge in the code, even if the rest of your project is not using this new feature at all.

FAQs

What is the expressiveness benefit of C# 8.0 nullable references beyond detecting bugs? Nullable references improve code expressiveness by requiring every variable, parameter, field, and property to clearly state whether null is an acceptable value. This information, which developers always had to know mentally when writing code, is now recorded in the source for anyone reading it later. This helps future readers, including your future self, understand the code without guessing what the original author had in mind.
What are nullable annotation contexts and nullable warning contexts in C# 8.0? C# 8.0 provides two independently controllable aspects of nullable support. The nullable annotation context determines whether you can express nullability (string vs string?) in your declarations. The nullable warning context determines whether the compiler generates nullability warnings. Each can be enabled or disabled separately using #nullable directives in source files or Nullable settings in the project file.
How can you enable nullable annotations without enabling warnings? You can enable just the ability to express nullability without seeing warnings by adding #nullable enable annotations to a source file, or by putting annotations in your project file. This lets you document nullability intent in your code while deferring the work of addressing warnings until later.
How can you enable nullable warnings without requiring annotations? You can enable only warnings without having to annotate your own declarations by adding #nullable enable warnings to a source file, or by putting warnings in your project file. In this case, all references in your own code are treated as null-oblivious, just like code from external libraries built without nullable annotations.
Why should developers start using nullable annotations even before fully enabling the feature? Even if you are not ready to deal with all the warnings from fully enabling nullable references, you can still benefit from the expressiveness. When writing new code, you already know whether null is a possibility, so recording this knowledge costs little extra effort. You can write new code in an enabled nullable annotation context while leaving the rest of the project unchanged.

Ian Griffiths

Technical Fellow I

Ian Griffiths

Ian has worked across an extraordinary breadth of computing - from embedded real-time systems and broadcast television to medical imaging and cloud-scale architectures. As Technical Fellow at endjin, he brings this deep cross-domain experience to bear on the hardest technical problems.

A 17-time Microsoft MVP in Developer Technologies, Ian is the author of O'Reilly's Programming C# 12.0 and one of the foremost authorities on the C# language and high-performance .NET development. He's a maintainer of Reactive Extensions for .NET, Reaqtor, and endjin's 50+ open source projects.

Ian has created Pluralsight courses on WPF fundamentals, WPF advanced topics, WPF v4, and the TPL, and has given over 20 talks at conferences worldwide. Technology brings him joy.