DEV Community

Cover image for Safer Code with C# 8 Non-Null Reference Types
Matt Eland
Matt Eland

Posted on

Safer Code with C# 8 Non-Null Reference Types

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.

Null reference warning

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>
Enter fullscreen mode Exit fullscreen mode

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:

Warning on returning null

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;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (7)

Collapse
 
cubiclebuddha profile image
Cubicle Buddha

I left C# years ago when I found out that TypeScript already had non-nullable reference types. I’ve been very happy and productive since then with my NodeJS services. Even my ORM stuff has been a joy. I might return to C# libraries start to migrate to C# 8.

Matt, how long do you think it will be before LINQ to SQL integrates this? I know that NodaTime already has started porting to C#8’s non-nullable ref types.

Collapse
 
integerman profile image
Matt Eland

TypeScript is actually my favorite programming language, though I miss LINQ. F# is pretty cool, but I'm still learning the ins and outs of it.

With .NET Core 3 expected to be official around the 23rd - 25th, I think we're most likely to see additional polish and support around the feature alongside 3.1 or a stand-alone 'service pack' style update to Visual Studio. I'd be surprised if we had to wait until Visual Studio 2021 to get more royal support for non-null types, but anything is possible.

When I look at all of the options for improving code quality out there, this feature is probably the one I'd recommend to my department once the IDE supports it more out of the box. Until then, I'm pushing for use of Jetbrains.Annotations in critical areas for more explicit null checking on a large legacy codebase.

Collapse
 
cubiclebuddha profile image
Cubicle Buddha

Yea, ImmutableJS has helped fill the gap left by LINQ. But it doesn’t do SQL generation, so I’ve been fine with TypeORM (which is actually pretty stellar).

And you’re right about the Jetbrains.Annotations. They’re basically a must-have.

Thread Thread
 
integerman profile image
Matt Eland

I've found Jetbrains.Annotations to be a subjective thing. While I love them, as a senior / lead dev on a team, I'd only use them in areas largely maintained by me, not the more shared areas of our codebase. Now as a technology manager, I get to set standards and it's part of the standard I set.

I wasn't aware ImmutableJS did anything beyond help freeze your objects. I love it for that. Does it offer advanced querying capabilities or other capabilities from things like underscore or lodash?

Thread Thread
 
cubiclebuddha profile image
Cubicle Buddha

ImmutableJS has Seq<T> which offers you lazy-evaluation so you can write functional chains while reducing the number of iterations (just like LINQ does).

Collapse
 
jonasbarka profile image
Jonas Barkå

I'm really looking forward to start adding this to code!

Collapse
 
lurezu80 profile image
lurezu80

Hello to me it would be something new in my developments, I want to use it, thanks