Skip to content

04 - Nullable Reference Types

Nullable Reference Types

Classically reference types are nullable (i.e. objects can always be null)
This causes often runtime errors (null reference exception)
Nullable reference types and static code analysis tries to help

Null and non-null values

A reference isn't supposed to be null:

  • The variable must be initialized to a non-null value.
  • The variable can never be assigned the value null.

A reference may be null:

  • The variable may only be dereferenced when the compiler can guarantee that the value isn't null.
  • These variables may be initialized with the default null value and may be assigned the value null in other code.

Warning

This is purely compile time feature, during execution everything is still nullable. No additional checks or emitted code.

Setup & Requirements

Starting with C# 8.0

Opt in feature:

  • Mandatory in this course and next (Distributed App Development)
  • Create file named “Directory.Build.props” in solution root folder
  • Convert warnings to errors
1
2
3
4
5
6
7
<Project>
    <PropertyGroup>
        <LangVersion>latest</LangVersion>
        <Nullable>enable</Nullable>
        <WarningsAsErrors>CS8600,CS8602,CS8603,CS8613,CS8618,CS8625</WarningsAsErrors>
    </PropertyGroup>
</Project>

Declaration

Declaration – add ? to the type of variable.

1
2
String? foo = null; // ok
String bar = null; // error

Any variable where ? isn’t appended to type is non-nullable ref type.

Null forgiving operator - !

1
Foo!.Length;

?: ?. ?[] ?? ??=

  • ?: - conditional operator (ternary conditional operator)
    classify = (input > 0) ? "positive" : "negative";
  • ?. and ?[] - null-conditional Operators
    int? length = customers?.Length;
    Customer first = customers?[0];
    int? count = customers?[0]?.Orders?.Count();
  • ?? - null-coalescing operator
    int count = customers?[0]?.Orders?.Count() ?? 0;
  • ??= - null-coalescing assignment operator
    Instead of:
    if (variable is null) { variable = expression; }
    variable ??= expression;

Null ref – Late initialization

Solutions:

  • Initialize in constructor
  • Use backing field with checks
  • Initialize property to null! or default!
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class Person
{
    public Person()
    {
        Name2 = "foo";
    }

    public string Name1 { get; set; } // Error, not initialized
    public string Name2 { get; set; } // Ok, initialized in ctor
    private string? _name3;
    public string Name3
    {
        get => _name3 ?? throw new NullReferenceException();
        set => _name3 = value;
    }
    public string Name4 { get; set; } = default!; // or = null!}
}

Null ref - descriptive options

More descriptive options can be added via attributes.

AllowNull - A non-nullable input argument may be null.
DisallowNull - A nullable input argument should never be null.
MaybeNull - A non-nullable return value may be null.
NotNull - A nullable return value will never be null.
MaybeNullWhen - A non-nullable input argument may be null when the method returns the specified bool value.
NotNullWhen - A nullable input argument will not be null when the method returns the specified bool value.
NotNullIfNotNull - A return value isn't null if the argument for the specified parameter isn't null.
DoesNotReturn - A method never returns. In other words, it always throws an exception.
DoesNotReturnIf - This method never returns if the associated bool parameter has the specified value.

Microsoft documentation

Null ref - attributes

ScreenName – allow to set value to null, but get never returns null
ReviewComment – can be null but cannot be set to null

Post-conditions

  • Since T? is not allowed specify possible null return value
  • You can pass in null value, but return value is not null
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Person
{
    [AllowNull]
    public string ScreenName
    {
        get => _screenName;
        set => _screenName = value ?? GenerateRandomScreenName();
    }
    private string _screenName = GenerateRandomScreenName();

    [DisallowNull]
    public string? ReviewComment
    {
        get => _comment;
        set => _comment = value ?? throw new ArgumentNullException(nameof(value), "Cannot set to null");  
    }
    string? _comment;

    [return: MaybeNull]
    public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { }

    public void EnsureCapacity<T>([NotNull] ref T[]? storage, int size) { }
}