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,
- Nullable annotation context: This enables/disables the nullable annotation (?) for nullable reference types.
- Nullable warning context : This enables/disables the compiler warnings for nullable reference types.
<Nullable>enable</Nullable>
Value for Nullable in Project Files:
- enable: Nullable annotations are enabled, and nullable warnings are enabled.
- disable : Nullable annotations are disabled, and nullable warnings are disabled.
- warning: Nullable annotations are disabled, and nullable warnings are enabled.
- 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:
- enable: Annotations are enabled, and warnings are enabled.
- disable: Annotations are disabled, and warnings are disabled.
- restore : Restores all settings to the project settings.
- disablewarnings : Warnings are disabled, and annotations are unaffected.
- enablewarnings : Warnings are enabled, and annotations are unaffected.
- restorewarnings: Warnings reset to project settings annotations are unaffected.
- disableannotations: Annotations are disabled, and warnings are unaffected.
- enableannotations: Annotations are enabled, and warnings are unaffected.
- 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");