C# 8 Nullable Reference Types

C# 8.0 will introduce a new groundbreaking feature called Nullable reference types, this feature will change the way we currently develop our software in C# by making, you guessed it, reference types nullable.

This post serves as a practical introduction to the problem and how you should solve those problems.

Why should we use it?

There are multiple benefits in using this feature. The first and foremost is type safety for reference types. In the end it is very weird that we can assign a value to a reference type that is not of that type. For example:

string helloNull = null; 

Will cause a compiler butt kick, because null is not a string!

The second benefit is that it will make the intent of your code very clear. It tells you and the users of your solution how your code should be used and handled. In the blink of an eye you’ll see what this code should do, and if your are doing it wrong the compiler tell you.

The whole ecosystem including all the libraries that we all use will eventually be safer to use because they generate no null reference error and the intent of the libraries is also much more clear!

If you are still not convinved that null is a bad idea, then the creator of the billion dollar mistake also know as null has publicly apolizeged for introducing it.

Enabling

As this is a breaking change we need to enable it. Add the following to your csproj or maybe even better to your Directory.build.props:

<LangVersion>8</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NullableReferenceTypes>true</NullableReferenceTypes>

Also make sure your that all your projects target .NET Core 3.0 or higher.

Welcome to .NET Hardcore

.NET Hardcore

Errors

When .NET Hardcore mode is on we will find lots of errors in our current codebase. We are only fixing the nullable references type which should be explicit. In the end it means when we fix these errors we will get to the real bugs, that only you can fix.

Nullable parameters

When we give a method a nullable parameter often we mean that this parameter is optional.

public class AuthSettings
{
public AuthSettings(Uri apiBase, string token, string username = null)
{
ApiBase = apiBase;
Token = token;
Username = username;
}
}

The error we would now run into is:

CS8625 Cannot convert null literal to non-nullable reference or unconstrained type parameter.

To fix this can make Username nullable by adding the ?. Note that we also have to do this for the property.

public class AuthSettings
{
public AuthSettings(Uri apiBase, string token, string? username)
{
Username = username;
//Handle other params
}
public string? Username { get; }
}

Non-nullable properties that are uninitialized

When we create properties we often don’t think about the consequences and if they should be nullable or not. For example we could have:

public class PullRequestRequest
{
public PullRequestRequest()
{
}
public string Body { get; set; }
}

Which is fine in the old world but it will now generate an error that you are almost definitely our going to encounter:

CS8618 Non-nullable property ‘PropertyName’ is uninitialized.
CS8618 Non-nullable field ‘FieldName’ is uninitialized.

To fix these we should decide if this property should be nullable or not. If its nullable we append the ? to it:

public string Body? { get; set; }

If it should not be nullable we need to set it! So either give a parameter that initiliazes it or set a default value:

public class PullRequestRequest
{
public PullRequestRequest(string body)
{
Body = body;
}
public string Body { get; set; }
}
//OR
public class PullRequestRequest
{
public PullRequestRequest()
{
Body = "defaultBodyValue";
}
public string Body { get; set; }
}

Returning null

Sometimes we tend to return null because we didn’t do anything with the results:

public static IReadOnlyCollection<string> FirstPopulatedList(List<string> list1, List<string> list2)
{
if (HasElements(list1))
return list1;
if (HasElements(list2))
return list2;
return null;
}
view raw returnnull.cs hosted with ❤ by GitHub

The error we encounter is:

CS8603 Possible null reference return.

You could argue that this might be a code smell but to fix it we should always return a initiliazed list or we should change the signature to make it return null:

public static IReadOnlyCollection<string> FirstPopulatedList(List<string> list1, List<string> list2)
{
if (HasElements(list1))
return list1;
if (HasElements(list2))
return list2;
return new List<string>();
}
//OR
public static IReadOnlyCollection<string>? FirstPopulatedList(List<string> list1, List<string> list2)
{
if (HasElements(list1))
return list1;
if (HasElements(list2))
return list2;
return null;
}

Another interresting case for this error might be that you are checking for null values, and if they are null, you are returning null as well:

public static Uri EnsureTrailingSlash(Uri uri)
{
if (uri == null)
return null;
return new Uri(uri.ToString() + "/");
}

To fix it, either decide to make your parameter nullable or to make it explicit:

public static Uri EnsureTrailingSlash(Uri uri)
{
return new Uri(uri.ToString() + "/");
}
//OR
public static Uri EnsureTrailingSlash(Uri? uri)
{
if(uri == null)
return null;
return new Uri(uri.ToString() + "/");
}

Possible dereference of a null reference

This is the hardest one i found so far, and i didn’t fully understand it yet. An example of where i encountered this error is:

public async Task<SearchCodeResult> Search(SearchCodeRequest search)
{
foreach (var repo in search.Repos)
{
//do search stuff
}
}
class SearchCodeRequest{
public IList<string>? Repos { get; set; }
}

CS8602 Possible dereference of a null reference.

Note that the list is nullable, so to fix this we should check if the list is != null. What is really going on is that the compiler is warning us that the reference value may be mutated to null.

Conclusion

While doing this work a lot of your codebase while change and errors will bubble up to the real problem you got to fix. This mostly means deciding if something should be null or not. When that is done your codebase quality is improved and the intent will be much clearer.

Nullable reference types seem like a great way forward and i can’t wait till everbody is using this feature to make our ecosystem so much better.