Nullable reference types

C# 8.0 introduces nullable reference types and non-nullable reference types that enable you to make important statements about the properties for reference type variables:

  • A reference is not supposed to be null. When variables aren't supposed to be null, the compiler enforces rules that ensure it is safe to dereference these variables without first checking that it isn't 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. When variables may be null, the compiler enforces different rules to ensure that you've correctly checked for a null reference:
    • 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.

This new feature provides significant benefits over the handling of reference variables in earlier versions of C# where the design intent couldn't be determined from the variable declaration. The compiler didn't provide safety against null reference exceptions for reference types:

  • A reference can be null. No warnings are issued when a reference type is initialized to null, or later assigned to null.
  • A reference is assumed to be not null. The compiler doesn't issue any warnings when reference types are dereferenced. (With nullable references, the compiler issues warnings whenever you dereference a variable that may be null).

With the addition of nullable reference types, you can declare your intent more clearly. The null value is the correct way to represent that a variable doesn't refer to a value. Don't use this feature to remove all null values from your code. Rather, you should declare your intent to the compiler and other developers that read your code. By declaring your intent, the compiler informs you when you write code that is inconsistent with that intent.

A nullable reference type is noted using the same syntax as nullable value types: a ? is appended to the type of the variable. For example, the following variable declaration represents a nullable string variable, name:

string? name;

Any variable where the ? is not appended to the type name is a non-nullable reference type. That includes all reference type variables in existing code when you have enabled this feature.

The compiler uses static analysis to determine if a nullable reference is known to be non-null. The compiler warns you if you dereference a nullable reference when it may be null. You can override this behavior by using the null-forgiving operator (!) following a variable name. For example, if you know the name variable isn't null but the compiler issues a warning, you can write the following code to override the compiler's analysis:

name!.Length;

You can read details about this operator in the draft nullable reference types specification proposal on GitHub.

Nullability of types

Any reference type can have one of four nullabilities, which describes when warnings are generated:

  • Nonnullable: Null can't be assigned to variables of this type. Variables of this type don't need to be null-checked before dereferencing.
  • Nullable: Null can be assigned to variables of this type. Dereferencing variables of this type without first checking for null causes a warning.
  • Oblivious: This is the pre-C# 8 state. Variables of this type can be dereferenced or assigned without warnings.
  • Unknown: This is generally for type parameters where constraints don't tell the compiler that the type must be nullable or nonnullable.

The nullability of a type in a variable declaration is controlled by the nullable context in which the variable is declared.

Nullable contexts

Nullable contexts enable fine-grained control for how the compiler interprets reference type variables. The nullable annotation context of any given source line is enabled or disabled. You can think of the pre-C# 8 compiler as compiling all your code in a disabled nullable context: Any reference type may be null. The nullable warnings context may be set to enabled, disabled, or safeonly. The nullable warnings context specifies the warnings generated by the compiler using its flow analysis.

The nullable annotation context and nullable warning context can be set for a project using the NullableContextOptions element in your csproj file. This element configures how the compiler interprets the nullability of types and what warnings are generated. Valid settings are:

  • enable: The nullable annotation context is enabled. The nullable warning context is enabled.
    • Variables of a reference type, string for example, are non-nullable. All nullability warnings are enabled.
  • disable: The nullable annotation context is disabled. The nullable warning context is disabled.
    • Variables of a reference type are oblivious, just like earlier versions of C#. All nullability warnings are disabled.
  • safeonly: The nullable annotation context is enabled. The nullable warning context is safeonly.
    • Variables of a reference type are nonnullable. All safety nullability warnings are enabled.
  • warnings: The nullable annotation context is disabled. The nullable warning context is enabled.
    • Variables of a reference type are oblivious. All nullability warnings are enabled.
  • safeonlywarnings: The nullable annotation context is disabled. The nullable warning context is safeonly.
    • Variables of a reference type are oblivious. All safety nullability warnings are enabled.

You can also use directives to set these same contexts anywhere in your project:

  • #nullable enable: Sets the nullable annotation context and nullable warning context to enabled.
  • #nullable disable: Sets the nullable annotation context and nullable warning context to disabled.
  • #nullable safeonly: Set the nullable annotation context to enabled, and the warning context to safeonly.
  • #nullable restore: Restores the nullable annotation context and nullable warning context to the project settings.
  • #pragma warning disable nullable: Set the nullable warning context to disabled.
  • #pragma warning enable nullable: Set the nullable warning context to enabled.
  • #pragma warning restore nullable: Restores the nullable warning context to the project settings.
  • #pragma warning safeonly nullable: Sets the nullable warning context to safeonly.

The default nullable annotation and warning contexts are disabled. That decision means that your existing code compiles without changes and without generating any new warnings.

The differences between the enabled and safeonly nullable warning contexts are warnings for assigning a nullable reference to a non-nullable reference. The following assignment generates a warning in an enabled warning context, but not a safeonly warning context. However, the second line, where s is dereferenced, generates a warning in a safeonly context:

string s = null; // warning when nullable warning context is enabled.
var txt = s.ToString(); // warning when nullable warnings context is safeonly, or enabled.

Nullable annotation context

The compiler uses the following rules in a disabled nullable annotation context:

  • You can't declare nullable references in a disabled context.
  • All reference variables may be assigned to null.
  • No warnings are generated when a variable of a reference type is dereferenced.
  • The null-forgiving operator may not be used in a disabled context.

The behavior is the same as previous versions of C#.

The compiler uses the following rules in an enabled nullable annotation context:

  • Any variable of a reference type is a non-nullable reference.
  • Any non-nullable reference may be dereferenced safely.
  • Any nullable reference type (noted by ? after the type in the variable declaration) may be null. Static analysis determines if the value is known to be non-null when it is dereferenced. If not, the compiler warns you.
  • You can use the null-forgiving operator to declare that a nullable reference isn't null.

In an enabled nullable annotation context, the ? character appended to a reference type declares a nullable reference type. The null forgiveness operator (!) may be appended to an expression to declare that the expression isn't null.

Nullable warning context

The nullable warning context is distinct from the nullable annotation context. Warnings can be enabled even when the new annotations are disabled. The compiler uses static flow analysis to determine the null state of any reference. The null state is either not null or maybe null when the nullable warning context isn't disabled. If you dereference a reference when the compiler has determined it's maybe null, the compiler warns you. The state of a reference is maybe null unless the compiler can determine one of two conditions:

  1. The variable has been definitely assigned to a non-null value.
  2. The variable or expression has been checked against null before de-referencing it.

The compiler generates warnings whenever you dereference a variable or expression in a maybe null state when the nullable warning context is enabled or safeonly. Furthermore, warnings are generated when a maybe null variable or expression is assigned to a nonnullable reference type when the nullable annotation context is enabled.

Learn more