Safer Code with C# 8 Non-Null Reference Types
Matt Eland
Posted on September 14, 2019
Null reference exceptions are one of the most frequent errors encountered in .NET applications. As powerful as the framework is, it's built around a core assumption that reference types can point to null, so any code that works with a reference type needs to either know already that the object is not null or do an explicit check.
Because objects being null often only occurs in very rare scenarios, it becomes easy not to catch these types of errors during development or even testing. This means that the .NET Framework and C# language is structured around something that makes it easy for bugs to hide deep in the code.
What we need is for the language to tell us about these problems before they can happen.
C# 8's nullable reference types feature aims to fix this.
What are Non-Null Reference Types?
First, a disclaimer - we've had nullable reference types for some time. That's the problem. The new feature is really the non-null reference types we have to work with, but the documentation prefers to refer to the new features as nullable reference types.
The nullable reference types feature takes every existing reference type and assumes that it is non-null by default at the editor level. Risky code will still compile, but you will get warnings on things that are initialized as null, could potentially be null, etc.
Because null is still an important concept you will still be able to represent potentially null types, but you will have to explicitly say that the type could be null because reference types will be assumed to be non-null by default.
This is somewhat similar to how TypeScript works, if you are familiar with nullability in TypeScript.
Enabling Non-Null Reference Types
To turn this feature on, at least at the time of this writing, you will have to manually edit your .csproj file of every project you want to enable it in. You will also need to be using Visual Studio 2019 or .NET Core 3.0 or higher.
Specifically, you will need to add two new properties to any PropertyGroup
: LangVersion
and Nullable
:
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>MattEland.SoftwareQualityTalk</AssemblyName>
<RootNamespace>MattEland.SoftwareQualityTalk</RootNamespace>
<LangVersion>8.0</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
LangVersion tells C# to use the C# 8 compiler.
Nullable tells C# to assume that all declared reference types are non-null by default.
What does this get us?
When you activate nullable reference types, you're going to see a lot of warnings in Visual Studio that various values could potentially be null or return null.
Specifically, you're going to see a lot of warnings that look like this:
This is pointing out that the method above declares that it returns a ResumeKeyword
return type. Previously this was a nullable reference type by default, now it is assumed to be non-null unless otherwise specified. To fix this, we change the return type to ResumeKeyword?
with the ?
indicating that the instance may not be present.
The correct code reads as follows:
private static ResumeKeyword? FindKeyword(IDictionary<string, ResumeKeyword> keywordBonuses, string key)
{
if (keywordBonuses.ContainsKey(key))
{
return keywordBonuses[key];
}
return null;
}
The other thing you're going to see a lot of warnings about is non-null properties and variables not being initialized. To fix this, you can either assign these a good initial value or you can change their variable type to indicate the value can be null using the ?
operator we used above.
It is important to understand that non-null reference types do not fundamentally alter the generated code in any way as they are a development convenience only. But the added value mixed with the simplicity of the code is huge and worth the investment.
Migrating to Nullable Reference Types
You can expect a large range of warnings like those above in your code when first turning on nullable reference types.
If you need to migrate bit by bit, you can revert to the old behavior in certain portions of your code by using the new #nullable disable
preprocessor directive like the following example illustrates:
#nullable disable
public class KeywordData
{
public int Id { get; set; }
public string Keyword { get; set; }
public int Modifier { get; set; }
}
#nullable restore
Alternatively (and this is the Microsoft recommended way), you could not enable nullable reference types for your project except in classes you've already migrated over to support them. In this case you use #nullable enable
instead of disable
. This helps migrate the code bit by bit, at the price of potentially having a great many nullable directives throughout your code.
That code would look like the following:
#nullable enable
public class KeywordData
{
public int Id { get; set; }
public string? Keyword { get; set; }
public int Modifier { get; set; }
}
#nullable restore
See Microsoft's upgrade guide for more comprehensive information on upgrading existing code to work with nullable reference types.
Conclusion
I strongly recommend giving this feature a try in a small-scale experiment and seeing what you think. The potential to eliminate defects by encouraging you to be explicit about your code's behavior is massive.
If you want a heavier-handed approach that enforces nullability at the compiler level, read my article on using the Option class from the Language-Ext library to catch problems at the compiler level, but note that the syntax is a lot more cumbersome than C# 8 non-null reference types.
If you like the idea of using attributes to annotate nullability on parameters and properties, check out my article on the JetBrains.Annotations package.
Overall I like the simplicity of non-null reference types in C# 8 and am going to keep working with them until I find something even better. If you'd like to learn more about them, read the official documentation.
If you find something else that works better for you in building high-quality software, I'd love to hear about it as I'm always looking to learn better ways to eliminate entire classes of software defects.
Posted on September 14, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
January 22, 2023