Key Takeaways Changing C# versions require modifying the project file directly.

Globally enabling nullable reference types can only be done with the new project format.

Nullability can be changed on a per-file or per-line basis when needed.

Use nullability attributes to avoid unnecessary null checks.

Use the Nullable package when targeting older platforms.

While parts of C# 8 will never be supported in .NET Framework, the Nullable Reference Types can be turned on if you know the tricks.

Enabling C# 8

The first step is to ensure you are using Visual Studio 2019 version 16.3 or higher.

Next you need to configure your project for C# 8. If you are used to working with Visual Studio, you may expect to be able to simply change a project setting. Unfortunately, that doesn’t work anymore.



Under the new rules, the default version of C# is determined by which framework you are targeting. Only .NET Core 3.0 and .NET Standard 2.1 get C# 8, everything else starts with C# 7.3.

You can override this by editing the project file. Open your project’s .csproj file and add this line inside a PropertyGroup.

<LangVersion>8.0</LangVersion>

If you are using a modern project format, you can open it simply by double-clicking on the project in the Solution Explorer. You can recognize this format because the root of the XML file will look like this:

<Project Sdk="Microsoft.NET.Sdk">

If you are using the legacy project format, then editing it directly is a bit more complicated. One option is to close Visual Studio and use Notepad or another text editor. Alternately you can install Power Commands for Visual Studio, which adds an "Edit Project File" command. For reference, the root of the XML file will look something like this:

<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

Enabling Nullable Reference Types:

The next step is to turn on the nullable reference types feature. If you are using the modern project format, you can do this globally by adding the following line just after the language version.

<Nullable>enable</Nullable>

If you are using the legacy project format, this won’t work. You won’t get an error; the compiler just ignores the setting. So instead you’ll have to enable it on a file by file basis. To do this, add the nullable directive to the top of each file.

#nullable enable

This will also work in modern projects if you want to gradually enable the feature.

Nullability Attributes

There are a variety of attributes used to fine-tune the behavior of null checker. To explain, consider the function below:

bool TryGet(int key, out Customer? customer)

The implied contract is if this function returns true, the customer parameter will be not be null. Conversely, if the function returns false then customer will be null.

But that isn’t part of the signature, so if we were to use the function as-is, then we’d always need an extraneous null check on customer. The work-around is to add an attribute that clarifies the behavior:

bool TryGet(int key, [NotNullWhen(true)] out Customer? customer)

From the compiler’s perspective, this is saying customer will never be null if a true value is returned. It says nothing about what happens if a false value is returned, but that’s still enough to solve the signature problem.

In an article titled Try out Nullable Reference Types, Phillip Carter describes the various attributes in detail.

Conditional postconditions: MaybeNotNullWhen(bool), NotNullWhen(bool)

Dependent null-ness: NotNullIfNotNull

Flow: DoesNotReturn, DoesNotReturnIf(bool)

Postconditions: MaybeNull, NotNull

Preconditions: AllowNull, DisallowNull

Nullability Attributes in Older Projects

In order to actually use these attributes, they need to be defined. If you are using .NET Standard 2.1 or .NET Core 3.0, that’s already done for you. Otherwise you’ll have to create them as part of your project.

The traditional way of handling this would be to create a new .NET Core 3 project, the use F12 to reverse engineer the desired attributes. Then you can paste it into your project and add any missing details, which for attributes only takes a few minutes.

An easier way is to simply install the Nullable package by Manuel Römer. This will copy the NullableAttributes.cs file into your project, which includes all of the necessary attributes. This package has no compiled component, it is just the one source code file.

Either way, ensure all of the attributes are marked as "internal", not public. This is to avoid collisions with other libraries that may also be back-porting the attributes.

Further reading

About the Author

Jonathan Allen got his start working on MIS projects for a health clinic in the late 90's, bringing them up from Access and Excel to an enterprise solution by degrees. After spending five years writing automated trading systems for the financial sector, he became a consultant on a variety of projects including the UI for a robotic warehouse, the middle tier for cancer research software, and the big data needs of a major real estate insurance company. In his free time he enjoys studying and writing about martial arts from the 16th century.