This article is a part of the article series on C# New Features, and this article series will cover all the new features of C#, which have been introduced in the last 4-5 years along with upcoming new features. If we talk about language versions, then this series covers C# 6, C# 7.0, C# 7.1, C# 7.2, C# 7.3, and C# 8, and upcoming features of C# 9.0.
In the current article, we are going to discuss null values and null references handing in C#. Many new features and syntax have been introduced in C# from C# 6 to C# 8 versions to handle and manipulate null values and null references. Some more null values handling features are proposed in C# 9.0, which is the upcoming version of C#. Following is the list of improvements for null handling along with existing null handling options in C#, which we are going to discuss.
Before C# 6.0
- Nullable value type (‘nullable<value type>’ or ‘value type?’)
- Null Coalescing Operator (‘??’)
C# 6.0: New null handling operator
- Null conditional operator for member access (‘?.’)
- Null conditional operator for index-based access (‘?[]’)
C# 8.0: Multiple Changes for Null Handling
- Null-coalescing assignment (‘??=’)
- Non-nullable Reference Type
- Nullable reference Type (‘reference type?’)
- Null-forgiving operator (‘!’)
C# 9.0: Planned and upcoming feature for null handling
- Simplified parameter null validation
- Target typed null coalescing (`??`) expression
- Nullable-enhanced common type
- Ternary operation with nullable int and double
In the last 4-5 years, many new features have been introduced in C#, and if we talk about the number of features, including major and minor features, then it would be around a hundred, and to explain those hundred features and their real-time uses we need to understand and compare with the already existing features as well. So we will be discussing those features as well as when they are required.
Following is a pictorial representation of new & upcoming versions of C#.
Note. C# 9.0 is not officially released, and it is still in the discussion and development phase, so we cannot be sure of how many of those features will be part of the C# 9.0 official release. It may get changed. But I would like to discuss C# 9.0 proposed features as well so that we can have a better understanding of where we are moving for C# upcoming and future releases.
Let’s start with a brief overview of the existing features of C#, which are available before C# 6.0. That way we can have a better understanding when discussing new features. However, we are not covering the existing features in detail.
Nullable value type (‘nullable<value type>’ or ‘value type?’)
Nullable value type (‘nullable<value type>’ or ‘value type?’) is not from the beginning of C#. It was introduced with C# 2.0 to allow value types to accept the null value.
Syntax
Nullable<T> variable;
Example
Nullable<int> x = null;
Simplified Syntax
T? variable;
Example
int? x = null;
The latest version of Visual Studio also gives us a hint to simplify it.
Tips. Nullable<T> is a struct, and its fully qualified name is System.Nullable<T>
namespace System
{
//Omitted for brevity
[NonVersionableAttribute]
public struct Nullable<T> where T : struct
{
//Omitted for brevity
}
}
Null Coalescing Operator (‘??’)
If the left-hand side expression is not null, then it returns the value of the left-hand side expression; otherwise, it evaluates the right-hand side expression and returns the evaluated value of the right-hand side expression as a result.
Null Coalescing Operator (‘??’) is a very powerful operator, but it is underused in C# coding. I have seen in many real-time projects where people are writing long multiline codes for a null check rather than just using the Operator ‘??’.
Syntax
<left-hand side expression> ?? <right-hand side expression>
Example
private static int Add(int x, int? y)
{
if (y == null)
{
y = 0;
}
return x + (int)y;
}
It can be written as
private static int Add(int x, int? y)
{
return x + (y ?? 0);
}
Null conditional operator for member access (?.)
Syntax
<instanceName>?.<memberName>
We, as C# developers, have seen the below exception message multiple times.
System.NullReferenceException
'Object reference not set to an instance of an object.' The following is a screenshot of ‘System.NullReferenceException’ in debug mode.
Complete code
using System;
using static System.Console;
namespace BankeCSharpBook
{
class Program
{
static void Main(string[] args)
{
Customer customer = null;
WriteLine(customer.Id);
WriteLine("Execution completed");
}
}
class Customer
{
public int Id { get; set; }
}
}
So, we can see that we are trying to access a property from the instance that has a null reference, and that is why we are getting this exception message.
If we search online, we can find many discussions regarding the ‘System.NullReferenceException’ we can find a very significant number of discussions. Following is a screenshot that is taken from https://stackoverflow.com/ for the same.
How to minimize the ‘System.NullReferenceException’
We can use the “Null conditional operator for member access (?.)” to get rid of this exception and break the code.
Just replace the code
WriteLine(customer.Id);
with
WriteLine(customer?.Id);
Finally, the error is gone.
Many times, we know the reason for that, but if it occurs in a production environment, we will be in trouble because, after that, the complete unit of the functionality stops working if the error is not appropriately handled.
Using Null-conditional Operator (‘?.’) with Null-Coalescing operator(‘??’)
Syntax
<instanceName>?.<memberName> ?? <defaultvalue>
Just take the same example that we used earlier and have a look at the below code snippets,
using System;
using static System.Console;
namespace BankeCSharpBook
{
class Program
{
static void Main(string[] args)
{
Customer customer = null;
WriteLine($"customer name : {customer?.Name}");
WriteLine($"Name Length: {customer?.Name?.Length}");
WriteLine("Execution completed");
}
}
class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
}
Output
We can see that the “null reference exception” has been handled, and it is returning null instead of throwing the null reference exception.
However, it would be better if we display some custom values instead of null. So replace the below line of code
WriteLine($"customer name: {customer?.Name}");
WriteLine($"Name Length: {customer?.Name?.Length}");
With
WriteLine($"customer name: {customer?.Name ?? "not provided or null"}");
WriteLine($"Name Length: {customer?.Name?.Length ?? 0}");
Notes
- In this article, we have used string interpolation and static features as well during code representation, But we are not covering those features in this article; they will be covered in future articles
- The “Null conditional operator” is also known by other names such as “Safe navigator operator”, “Null propagation operator”, and “Elvis operator”.
NULL conditional operator for index-based access (?[])
So far, we have seen how we can handle the null reference exception and how to use the “null- Coalescing” operator along with the “null conditional operator”. However, the question arises of how we can handle the exception when working with Indexers because we cannot use (?.) with Indexers.
To work with Indexer, we have another syntax for “Null Conditional Operator”.
Syntax for Indexers
<instanceName>?[<memberName>]
Have a look at the below screenshot, and we will notice that we are getting the same exception, i.e. null reference exception with indexes as well.
Now replace the line
WriteLine($"Third odd number is: {oddNumbers[2]}");
By
WriteLine($"Third odd number is : {oddNumbers?[2]}");
And re-run the code, and we can see that it is no longer throwing an exception. Because now we are using a Null conditional operator for index-based access (?[])
Complete code
using static System.Console;
namespace IndexBasedNullConditinal
{
class Program
{
static void Main(string[] args)
{
int[] oddNumbers = { 1, 3, 5, 7, 9 };
oddNumbers = null; // I am assigning null value intentionally just for demo purposes but in real project it would be coming from somewhere else.
WriteLine($"Third odd number is : {oddNumbers?[2]}");
}
}
}
Null-coalescing assignment Operator (??=)
Null-coalescing assignment (??=) operator has been introduced with C# 8.0. We know that “Null-coalescing operator (??)” was already in C# before the C# 8.0 version, but it has some limitations, and it does not support the assignment.
Let's have a look at the below code snippet.
List<string> fruits = null;
(fruits ?? new List<string>()).Add("apple");
Console.WriteLine(fruits.Count);
Compile and run the above code snippet. We can see that it is throwing a null reference exception because the new instance that we are creating after checking with null-Coalescing is not being assigned. Thus the original variable “fruits” still has a null reference so we cannot add any value to that list reference.
So we are again facing the issue of null reference exception and the solution is “null-coalescing assignment (??=)” i.e. replace the code.
(fruits ?? new List<string>()).Add("apple");
By
(fruits ??= new List<string>()).Add("apple");
And re-run the code. We can see that the code is no longer throwing the “NullReferenceException” exception, and we are good to go. Following is the screenshot of the code and its output after using the Null-coalescing assignment “??=” operator.
If we try to achieve the same through the earlier version of the code, then we have to write like below code snippet.
List<string> fruits = null;
if (fruits == null)
{
fruits = new List<string>();
}
fruits.Add("apple");
Console.WriteLine(fruits.Count);
The above code snippet can be easily replaced by the below code snippet in C# 8.
List<string> fruits = null;
(fruits ??= new List<string>()).Add("apple");
Console.WriteLine(fruits.Count);
Complete code
using System;
using System.Collections.Generic;
using static System.Console;
namespace BankeCSharpBook
{
class Program
{
static void Main(string[] args)
{
List<string> fruits = null;
(fruits ??= new List<string>()).Add("apple");
WriteLine(fruits.Count);
}
}
class Customer
{
public int? Id { get; set; }
public string Name { get; set; }
}
}
Output
1
Nullable Reference Type and Non-Nullable Reference Type
So far, we have seen many scenarios where the exception of the null references may arise. You may be wondering if there is an option so that a reference type should accept a null value or not.
Yes, that is why in C# 8.0, we can create “nullable reference type” and “non-nullable reference type”.
A non-nullable reference type feature has been introduced with C# 8.0. Enabling non-nullable reference types in C# 8 is a breaking change, and we may need to make changes in our existing code to compile and work as intended. That is why it is not enabled by default, and we need to enable it by ourselves.
Let us have a look at the changed syntaxes.
Following is the complete code snippet for the same.
namespace NullableExample1
{
class Program
{
static void Main(string[] args)
{
//C# 8.0 -- nullable instance of Customer type
Customer? cust1 = new Customer();
//C# 8.0 -- non-nullable instance of Customer type
Customer cust2 = new Customer();
}
}
public class Customer
{
//C# 8.0 -- non-nullable string
public string CustomerId { get; set; }
//C# 8.0 -- nullable string
public string? Name { get; set; }
}
}
If we have not enabled the nullable type feature, then the above code gives compilation warnings, and we get warning messages like below.
The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
Enabling nullable and non-nullable reference type features in C# 8.0
Let us enable the feature to see if there is any warning after that or not.
Step 1. Edit the project file.
Step 2. Go to the <PropertyGroup> section in the “.csproj” file.
Step 3. Add the below line of code at the location highlighted in the screenshot.
<Nullable>enable</Nullable>
Step 4. Save the project file and go back to the application.
Now we can see that the warning messages disappeared from the Visual Studio Error List window.
But we may get other warning messages if we are not using non-nullable reference types correctly or if it is an error-prone code. Following is a screenshot from Visual Studio, which gives another warning after enabling the '#nullable' context.
Enabling the '#nullable' context and using the nullable and non-nullable reference types does not guarantee that we cannot get a null reference exception, but its probability can be minimized.
Do we need to worry about this breaking change?
No, we do not need to worry about this breaking change if we are migrating our existing C# application to use the latest version feature but do not want to use the '#nullable' context. Then it is okay we can use your existing C# code with the C# 8.0 version, and it works correctly in the same manner as working earlier and does not give any warning.
That’s why the '#nullable' context is disabled by default in C# 8.0. This means we can enjoy other features of C# 8.0 without opting for the '#nullable' context.
How is it beneficial?
To understand how it is beneficial to us, have a look at the below code snippet.
class Customer
{
public string CustomerId
{
get;
set;
}
public string Name
{
get;
set;
}
public string Address
{
get;
set;
}
public void Print()
{
WriteLine($"Customer Id is: {CustomerId.Length} character(s) long");
WriteLine($"Customer name : {Name.ToUpper()}");
WriteLine($"Address : {Address.Trim()}");
}
}
Now from the “Main()” method of a console app, create the instance of the Customer class and call the Print() method without initializing the properties of the customer class.
static void Main()
{
Customer customer = new Customer();
customer.Print();
}
As we can see the code was compiled successfully, but it is throwing the “NullReferenceException” exception at run time.
It would be better if we enabled the '#nullable' context, so that Visual Studio can give us a warning, and we can correct the code at compile-time, which helps in reducing run-time exceptions.
Use the same steps as discussed earlier to enable the '#nullable' context and enable it for the current project.
Now we can see in the screenshot below that it is giving warning messages.
Let’s fix it.
For demo purposes right now, assume that all the 3 properties can be null and so mark it as nullable properties intentionally.
public string? CustomerId { get; set; }
public string? Name { get; set; }
public string? Address { get; set; }
Now we can see that those warning messages disappear, but some new warning messages get appeared.
The above warning messages are evident because we are using “possible null reference” instances without checking for null values.
Now replace the code of the “Print()” method with the below line of codes.
public void Print()
{
WriteLine($"Customer Id is: {CustomerId?.Length ?? 0} character(s) long");
WriteLine($"Customer name: {Name?.ToUpper() ?? "N/A"}");
WriteLine($"Address: {Address?.Trim() ?? "N/A"}");
}
Complete code
using static System.Console;
namespace NonNullableExample2
{
class Program
{
static void Main()
{
Customer customer = new Customer();
customer.Print();
}
}
class Customer
{
public string? CustomerId { get; set; }
public string? Name { get; set; }
public string? Address { get; set; }
public void Print()
{
WriteLine($"Customer Id is: {CustomerId?.Length ?? 0} character(s) long");
WriteLine($"Customer name: {Name?.ToUpper() ?? "N/A"}");
WriteLine($"Address: {Address?.Trim() ?? "N/A"}");
}
}
}
Output
Now we can see how clear the code we have written is. We have also provided a default print value in case the property is null. For numeric type, we have provided the value zero(0), for the string, we have provided “N/A” as the default value.
What happens if we use a null check with a non-nullable reference type in the '#nullable' context?
Null check with a non-nullable reference does not give you any warning.
Does it stop us from assigning a null value to a “non-nullable” reference type?
No, by default, it does not stop you from assigning a null value to a non-nullable reference type.
Have a look at the below code
using static System.Console;
namespace NonNullableExample2
{
class Program
{
static void Main()
{
Customer customer = new Customer { CustomerId = null };
customer.Print();
}
}
class Customer
{
public Customer() => CustomerId = "-1";
public string CustomerId { get; set; }
//Omitted for brevity
public void Print()
{
WriteLine($"Customer Id is: {CustomerId?.Length} character(s) long");
//Omitted for brevity
}
}
}
In the above code, we can see that I am marking the “CustomerId” property as a non-nullable type, but still, we can assign a null value.
Customer customer = new Customer { CustomerId = null };
So far, we have discussed the basic scenario of “nullable” & “non-nullable” reference types in the '#nullable' context.
There are many more scenarios for “nullable” & “non-nullable” reference types in the '#nullable' context, which can explore by ourselves while using it. Discussing all those scenarios is not possible in this article; otherwise, it will become a very long article.
What is the “Nullable Enable” concept in C# 8.0
Enabling the '#nullable' annotations context is also known as the “Nullable Enable” concept in C# 8.0. And it can be done at the following levels,
- At project level
- At Class level
- At Block Level
Enabling '#nullable' annotations context at file or class level
We have already seen how we can enable the '#nullable' annotations context at the project level. Let's remove it from the project level and move to the class level.
Go to the project and remove the below line of code from there.
#nullable enable
Project file code after removing nullable enable
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
</Project>
Now go to the class file and enable it at the file level. Just add the below line at the class level.
#nullableenable
Following is the code snippet of the same.
using static System.Console;
#nullable enable
namespace NonNullableExample2
{
class Program
{
//Omitted for brevity
}
class Customer
{
//Omitted for brevity
}
}
As we have kept it at the top level, so it has been applied for both the classes in the file.
Enabling '#nullable' annotations context at a block-level
Syntax
#nullable enable
// Our code block for which we would like to enable #nullable annotation context
#nullable disable
Below is a code snippet for the same.
//Omitted for brevity
namespace NonNullableExample2
{
class Program
{
//Omitted for brevity
}
class Customer
{
//Omitted for brevity
public string CustomerId { get; set; }
public string? Name { get; set; }
#nullable enable
public string? Address { get; set; }
#nullable disable
public void Print()
{
//Omitted for brevity
}
}
}
Note. If we use the syntaxes outside the block, then it gives us a warning message for the same. The following is a screenshot explaining the same.
Null-forgiving operator (‘!’)
Null-forgiving operator (‘!’) is available in C# 8.0 and later versions. This operator is a unary operator. It is used as a postfix.
In fact ‘!’ is a logical negation operator, but there are some differences. If the operator ‘!’ is used as a prefix, then it is a “logical negation operator”, and if it is used at postfix, then it is a “null-forgiving operator”.
The Null-forgiving operator is compiling time only and does not have any effect on the run time. i.e., the expression “x!” and “x” are the same at the run time.
To understand it in a better way, let’s take the same class example which we used earlier.
class Customer
{
#nullable enable
public string? Name { get; set; }
public string? Address { get; set; }
public void Print()
{
WriteLine($"Customer name: {Name.ToUpper()}");
WriteLine($"Address: {Address?.Trim() ?? "N/A"}");
}
}
Just compile the above class, and we can see a warning message as follows.
Warning CS8602: Dereference of a possibly null reference.
But if we are confident enough and know that the “Name” property cannot be null in this context we can use the “Null-forgiving operator” to get rid of the warning.
class Customer
{
#nullable enable
public string? Name { get; set; }
public string? Address { get; set; }
public void Print()
{
WriteLine($"Customer name : {Name!.ToUpper()}");
WriteLine($"Address : {Address?.Trim() ?? "N/A"}");
}
}
There are some other uses of this operator as well. We can use it for Unit test cases as well.
So far, we have covered the null handling feature available till C# 8.0 version. Now let’s have a brief look at the proposed and upcoming features in C# 9.0, which are related to null handling.
Simplified parameter null validation (proposed in C# 9.0)
In C# 8.0
void AddCustomer(Customer customer)
{
if (customer is null)
{
throw new ArgumentNullException(nameof(customer));
}
_customers.Add(customer);
}
Proposed in C# 9.0
void AddCustomer(Customer customer!) => _customers.Add(customer);
Target typed null coalescing (`??`) expression (proposed in C# 9.0)
In C# 8.0 the following code snippet is allowed,
List<int> list = new List<int>();
IEnumerable<int> list2 = list;
And the following code snippet is also allowed in C# 8.0.
List<int> list = new List<int>();
IEnumerable<int> list2 = Array.Empty<int>();
But the below code snippet is not allowed in C# 8.0.
List<int> list = new List<int>();
IEnumerable<int> list2 = list ?? Array.Empty<int>();
Proposed in C# 9
The below code snippet should be allowed
List<int> list = new List<int>();
IEnumerable<int> list2 = list ?? Array.Empty<int>();
Nullable-enhanced common type pasting (proposed in C# 9.0)
This proposed feature is similar to the “Target typed null coalescing (`??`) expression” feature.
In C# 8.0.
bool expression = true;
int? x = expression ? 1 : (int?)null;
Proposed in C# 9.0,
bool expression = true;
int? x = expression ? 1 : null;
Ternary operation with nullable int and double pasting (proposed in C# 9.0)
In C# 8.0 the following code will not work,
bool expression = true;
int? x = 1;
double y = 2.0;
var z = expression ? x : y;
Compilation Error in C# 8.0
Error CS0173: The type of conditional expression cannot be determined because there is no implicit conversion between 'int?' and 'double.'
In C# 9.0, the above code snippet is proposed to compile, and this proposed feature is referred to as a “Ternary operation with nullable int and double.”
Note. some of the proposed features are similar and may get merged into a single feature at the time of the official release of C# 9.0.
Summary
In this article, we have covered the following topics
- Nullable value type (‘nullable<value type>’ or ‘value type?’)
- Null Coalescing Operator (‘??’)
- Null conditional operator for member access (‘?.’)
- Null conditional operator for index-based access (‘?[]’)
- Null-coalescing assignment (‘??=’)
- Non-nullable Reference Type with '#nullable' annotations context
- Nullable reference Type (‘reference type?’) with '#nullable' annotations context
- Enabling '#nullable' annotations context at the project level
- Enabling '#nullable' annotations context at class or block-level
- Null-forgiving operator (‘!’)
- Simplified parameter null validation
- Target typed null coalescing (`??`) expression
- Nullable-enhanced common type
- Ternary operation with nullable int and double