C# supports the concept of nullable data types. A nullable type can represent all the values of its underlying type, plus the value null.

Thus, if you declare a nullable bool, it could be assigned a value from the set {true,false,null}.

To define a nullable variable type, the question mark symbol (?) is suffixed to the underlying data type. Like a non-nullable variable, local nullable variables must be assigned an initial value before you can use them.

int? nullableInt = 0;
double? nullableDouble = 12.3;
bool? nullableBool = null;
char? nullableChar = 'a';
int?[] arrayOfNullableInts = new int?[10];


Using Nullable Value Types

In C#, the ? suffix notation is a shorthand for creating an instance of the generic System.Nullable<T> structure type.

The assigned value of a nullable type may be obtained directly or via the Value property.

static void LocalNullableVariablesUsingNullable()
 {
     Nullable<int> nullableInt = 10;
     Nullable<double> nullableDouble = 12.3;
     Nullable<bool> nullableBool = false;
     Nullable<char> nullableChar = 'a';
     Nullable<int>[] arrayOfNullableInts = new Nullable<int>[10];
 }


Using Nullable Reference Types

A significant feature added with C# 8 is support for nullable reference types.

Non-nullable reference types must be assigned a non-null value at initialization and cannot later be changed to a null value.

Nullable reference types use the same symbol (?) to indicate that they are nullable. However, this is not a shorthand for using System.Nullable<T> as only value types can be used in place of T.


Opting in for Nullable Reference Types

Support for nullable reference types is controlled by steting a nullable context. This an be as big as entire Project (by updating the project file) or as small as a few lines (by using compiler directives). There are also 2 contexts that can be set,

  1. Nullable annotation context: This enables/disables the nullable annotation (?) for nullable reference types.
  2. Nullable warning context : This enables/disables the compiler warnings for nullable reference types.

<Nullable>enable</Nullable>

Value for Nullable in Project Files:

  1. enable: Nullable annotations are enabled, and nullable warnings are enabled.
  2. disable : Nullable annotations are disabled, and nullable warnings are disabled.
  3. warning: Nullable annotations are disabled, and nullable warnings are enabled.
  4. annotations: Nullable annotations are enabled, and nullable warnings are disabled.

the <Nullable> element in the project file affects the entire project. To smaller parts of the Project, use the compiler directives are:

  1. enable: Annotations are enabled, and warnings are enabled.
  2. disable: Annotations are disabled, and warnings are disabled.
  3. restore : Restores all settings to the project settings.
  4. disablewarnings : Warnings are disabled, and annotations are unaffected.
  5. enablewarnings : Warnings are enabled, and annotations are unaffected.
  6. restorewarnings: Warnings reset to project settings annotations are unaffected.
  7. disableannotations: Annotations are disabled, and warnings are unaffected.
  8. enableannotations: Annotations are enabled, and warnings are unaffected.
  9. restoreannotations: Annotations are reset to project settings; warnings are unaffected.

Nullable Reference Types in Action

string -> cannot be null (compiler warning if you try)

string -> can be null (? grants permission)


Compile-time warning only, does not prevent runtime crash.


#nullable disable/restore to toggle per region.
 
static void Main(string[] args)
{
     string name = "dotnetguard"; // OK - non-nullable
     string? nickname = null; // OK - ? allows null
     string surname = null; // WARNING CS8600! - no ?, should not be null.

     string? cmd = null;
     RunTask(cmd); // WARNING CS8604! - Possible null argument!
}
// Runtime guard - use at method entry
static void RunTask(string command, string user = "operator")
{
     ArgumentNullException.ThrowIfNull(command);
}

Change Nullable Warnings to Errors

When you are ready to commit to nullable reference types, you can configure the nullable warnings as errors. To project file:

<PropertyGroup>
 <WarningsAsErrors>CS8604,CS8625</WarningsAsErrors>
</PropertyGroup>

If you want to treat all warnings related to nullable types to errors;

<WarningsAsErrors>Nullable</WarningsAsErrors>

Note : You can change the severity of any warning to an error, not just those regarding nullable. Change all warnings to errors;

<TreatWarningsAsErrors>true</TreatWarningsAsErrors>

The Null-Coalescing Operator

Any variable that might have a null value can make use of the C# "??" operator, which is formally termed the null-coalescing operator.

DatabaseReader dr = new DatabaseReader();
int myData = dr.GetIntFormDb();
// Upside equals to the following code
int? myData2 = dr.GetIntFormDb();
if (!myData2.HasValue)
 {
     myData2 = 10;
 }

The Null-Coalescing Assignment Operator

C# 8 introduced the null-coalescing assignment operator (??=). This operator assigns the left-hand side to the right-hand side only if the left-hand side is null.

int? nullableInt = null;
nullableInt ??= 12;
nullableInt ??= 14;

The nullableInt variable is initialized to null. The next line assigns the value of 12 to the variable since the left-hand is indeed null. The next line does not assign 14 to the variable since it is not null.

The Null Conditional Operators

C# includes the null conditional operator token (a question mark placed after a variable type but before an access operator) to simplify the previous error checking. Rather than explicitly building a conditional statement to check for null.

static void TesterMethod(string[] args)
 {
     Console.WriteLine($"you sent me {args?.Length} args");
 }

If the variable (args) is null, its call to the Length property will not throw a runtime error. If you want to print an actual value, you called leverage the null-coalescing operator to assign a default value as so!

Console.WriteLine($"you sent me {args?.Length ?? 0} args");