Navigating Non-Nullable Reference Types in C#: Enhance Code Safety

I hate NullReferenceException

But one thing I hate the most is checking variables when they are not supposed to be empty or null, especially in these two cases:

  • myString != null when the string argument is not specified as Nullable
  • myEnumerable != null when it is not supposed to be (especially for extension methods)

Fortunately, this should be over soon.

C# 8.0 has introduced that reference types are not null by default. What does this mean?

Types that are not specified as Nullable can no longer be equal to null, the same way nullable references are already handled for native classes (bool, int, float and so on).

int myInt1 = 2 // OK
int? myInt2 = 2 // OK

myInt1 = null // throw Exception
myInt2 = null // OK

How does it works on the code side?

public class Goat
{
    public string Color { get; set; }
    public string AwesomeName { get; set; }
}

public class MyTest 
{
    public function MyGoatTest 
    {
        var goat = new Goat();
        var goatName = goat.AwesomeName;
        // Here, goatName == null
    }
}

A Goat has 2 properties: Color and AwesomeName.

This leads to checking both properties each time to make sure neither is null, even if the declaring type is string and not string?.

So what is the point of making these properties non-null if they are always null at initialization?

Use ? to declare nullable

If both properties can be null, the best solution is to declare the type as nullable by adding the question mark ?.

public class Goat
{
    public string? Color { get; set; }
    public string? AwesomeName { get; set; }
}

It seems strange to add a question mark to a string type, but why not? We already do it for many types like bool, int and so on. Why not do it for all types?

In this way, the Goat object admits that the Color and AwesomeName properties can be null, and it makes sense to check with myString != null each time they are used to be sure they contain a value.

Forcing not nullable values

This is the easy part now!

Properties can no longer be equal to null, which means that... They have to be initialized!

The best known method is to use constructors to ensure that each property is defined.

public class Goat
{
    public Goat(string color, string awesomeName, Grassland grassland, string[] skills)
    {
        Color = color;
        AwesomeName = awesomeName;
        Grassland = grassland;
        Skills = skills;
    }

    public string Color { get; set; }
    public string AwesomeName { get; set; }
    public string[] Skills { get; set; }
    public Grassland Grassland { get; set; }
}
// Here is the beautiful Grassland class, awesome right?
public class Grassland 
{
	public int Surface { get; set; } = 0;
}

But constructors imply that you have all the data you need to build your object.

This is not always true. The fact is that a non-nullable property does not mean that it has to be set, it can also mean that it contains a default value.

For that, don't worry, we already have everything we need:

  • Strings with string.Empty
  • Types with constructors (do not use  default as initializer since it's equals to null for objects)
  • Enumerable from  with new List<T>/T[] as needed.
public class Goat
{
    public string Color { get; set; } = string.Empty;
    public string AwesomeName { get; set; } = string.Empty;
    public string[] Skills { get; set; } = Array.Empty<string>();
    public Grassland Grassland { get; set; } = new Grassland();
}

You can know be sure that each properties is different from null at 100%.

Handling methods return type

The point of keeping in mind the difference between T? and T is to avoid unnecessary conditions to check an element.

public string GetGoatFullName(Goat goat) {
    // Do not have to check if properties are null since it's declared at string not nullable
    return $"{goat.AwesomeName} {goat.Color}";
}

public void TestClass() {
    var myGoat = new Goat();
    var fullName = GetGoatFullName(goat);
    // Do not have to check if fullname is null
    if(fullName == "Chicky Orange") {
    	Console.Log("What an awesome name");
    }
}

GetGoatFullName() returns string, so the fullName != null condition will check for something that can no longer happen.

It may be worth checking for emptiness if, and only if, you need to check it. In this case, we don't mind if fullName is empty!

The code is, in my opinion, cleaner and more readable!

How does it works on the compilator side ?

This handling of Non-Nullable and Nullable generates attributes on compilator:

  • [DisallowNull] and [AllowNull] to specify preconditions on properties and methods arguments
  • [NotNull] and [MaybeNull] to specify postconditions on methods return values

To go further:

Attributes interpreted by the C# compiler: Nullable static analysis
Learn about attributes that are interpreted by the compiler to provide better static analysis for nullable and non-nullable reference types.

Null-forgiving operator

You may have seen one of these warnings:

  • Warning CS8602: Dereferencing a possibly null reference
  • Warning CS8625: Cannot convert null literal or possible null value to non-nullable type

Even if you have specified your T object as non-nullable, nothing (for the moment) prevents you from assigning the null value.

You mainly get these cases when you forget to initialize your properties.

As the opposite of the ? operator which converts a Non-Nullable to Nullable, the ! operator converts a Nullable to Non-Nullable.

This means that even though the result may be null, it is considered Non-Nullable.

The null-forgiving operator has no effect at run time. It only affects the compiler's static flow analysis by changing the null state of the expression. At run time, expression x! evaluates to the result of the underlying expression x.

Here is a great link to learn more about this :

! (null-forgiving) operator - C# reference
Learn about the C# null-forgiving, or null-suppression, operator that is used to declare that an expression of a reference type isn’t null.

Warning: I do not recommend using this, but it's worth it to understand how it works!

I hope you've learned something. If you did, don't forget to subscribe to be aware of all news articles in coming!