What’s New in C# 14: Null-Conditional Assignments

If you’ve ever developed in C#, you’ve likely encountered a snippet like the one below:

if (config?.Settings is not null) 
{
    config.Settings.RetryPolicy = new ExponentialBackoffRetryPolicy();
}

This check is necessary because, if config or config.Settings is null, a NullReferenceException is thrown when trying to set the RetryPolicy property.

But no more endless ifs! The latest version of C#, scheduled for release later this year with .NET 10, introduces the null-conditional assignment operators, which are designed to solve this exact issue.

Wait, Doesn’t C# Already Have Null-Conditionals?

Yes! The null-conditional and null-coalescing operators have been around for a while. They simplify checking if a value is null before assigning or reading it.

// Null-conditional (?.)
if (customer?.Profile is not null) 
{
    // Null-coalescing (??)
    customer.Profile.Avatar = request.Avatar ?? "./default-avatar.jpg";
}

However, these operators only worked when reading a value, not setting one. With C# 14, you’ll be able to use it on the left-hand side of the = operator! Depending on your codebase, this can drastically clean up your code by reducing the number of if statements needed to assign values.

Prerequisites

C# 14 is still in development. This means that the syntax might change slightly before the final release of .NET 10. However, I’ll update this article if anything does change before the final release.

You’ll need to download and install the latest .NET 10 preview SDK to run the snippets below. Next, create a new C# project (using a Console project in this article) and ensure it targets .NET 10. You’ll also need to enable preview language features by opening your *.csproj file and adding the <LangVersion>preview</LangVersion> tag:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <!-- Add the LangVersion tag below. -->
    <LangVersion>preview</LangVersion>
  </PropertyGroup>
</Project> 

You’re all set to follow along with the snippets below.

Null-Conditional Assignments With Object Properties

The introduction illustrated a typical pattern you’ll find in C# projects when assigning values to object properties: first check if the object itself is not null. If you don’t, a frustrating NullReferenceException gets thrown, and those are no fun to debug.

Take the snippet you saw at the beginning of this article:

if (config?.Settings is not null) 
{
    config.Settings.RetryPolicy = new ExponentialBackoffRetryPolicy();
}

This becomes a one-liner in C# 14:

config?.Settings?.RetryPolicy = new ExponentialBackoffRetryPolicy();

Now, the compiler will first make sure that config?.Settings is not null before proceeding to assign the RetryPolicy property. If config?.Settings is null, the assignment is skipped.

Null-Conditional Assignments With Indexer Elements

The new operator also works on indexers. This is useful when you’re unsure whether a dictionary or list is null before assigning a value.

Below is how you’d currently check if a dictionary is defined before assigning a value to it:

if (customerData is not null)
{
    customerData["LastLogin"] = DateTime.UtcNow;
}

Again, this is much simpler in C# 14:

customerData?["LastLogin"] = DateTime.UtcNow;

Similar to the first example, the compiler will first check if properties is defined. If it is, the assignment is executed; otherwise, it’s ignored.

Null-Conditional Assignments with Compound Assignments

You’re not limited to just using standard = assignments. You can also use the new operator with compound assignment operators like += and -=.

Previously, you would have probably written something similar to the snippet below:

if (results is not null)
{
    results.ItemsProcessed += 5;
}

This now becomes:

results?.ItemsProcessed += 5;

You can even combine it with other operators, like the null-coalescing assignment operator (??=), to handle cases where the object exists but the property itself is null.

customer?.Name ??= "Guest";

In the one-liner above, if customer is null, the entire assignment is skipped. Otherwise, if customer is defined, but Name is null, then the property is assigned "Guest".

Things To Keep In Mind

Before applying null-conditional assignments throughout your codebase, keep these things in mind.

Side-Effect Prevention

When a null-conditional statement assignment is evaluated, the right-hand side of the expression is not executed unless the left-hand side is defined. This is to prevent side effects arising from the right-hand side executing, but then resulting value is being discarded when performing the assignment.

For example, you might have a function that generates the next ID in a sequence and returns it:

customer?.Id = GenerateNextCustomerId();

If customer is null, then GenerateNextCustomerId() won’t be executed. This is logical, since you don’t want to unnecessarily increment your customer ID counter if the value isn’t going to be used. Nevertheless, it’s good to keep in mind when utilizing the operator.

Increment and Decrement Operators Aren’t Supported

Sometimes it’s convenient to increment a value using the ++ or -- operators. However, those won’t work if you’re using a null-conditional assignment operator.

For example, this code snippet will break:

// Will fail as ++ and -- are not supported with null-conditional assignments
customer?.TotalOrders++;

Why not support it? The C# design team decided that these operators would be more challenging to implement and might not yield the expected results. Hence, a decision was taken not to support it.

Don’t Overuse It

This is my personal preference: avoid overusing the operator, as it might make it unclear why a specific value isn’t being assigned.

For example, consider the snippet below:

customer?.Orders?.FirstOrDefault()?.OrderNumber = GenerateNewOrderNumber();

If a customer reports that the order number is not showing, you would need to look at this single line of code and determine:

  • Did the GenerateNewOrderNumber() method return null
  • Was customer set to null? Maybe the query to retrieve it is incorrect.
  • Was customer.Orders set to null? Maybe the ORM never attached the order list to the customer entity.
  • Was the customer.Orders list empty, so FirstOrDefault() returned a null value?
  • Was the first value in the Orders list a null? This could indicate some deserialization issue somewhere.

It’s too many “what ifs” for one line. So, if your code looks like that, seriously consider refactoring it to be easier to debug. Perhaps to something like this:

if (customer is null)
{
    logger.LogWarning("Customer is null for {CustomerId}", customerId);
    return;
}

var firstOrder = customer.Orders?.FirstOrDefault();
if (firstOrder is null)
{
    logger.LogWarning("Could not find an order for customer {CustomerId}", customerId);
    return;
}

firstOrder.OrderNumber = GenerateNewOrderNumber();

This code makes it much easier to determine why an order number was not assigned, simply by reviewing the application logs.

Conclusion

The new null-conditional assignment operator is a convenient feature coming in C# 14. It lets you avoid nesting assignments inside if statements, making your code easier to read. However, keep in mind the limitation of not supporting ++ and -- operators, and try not to overuse the new operator.

This is just one of several enhancements being introduced with C# 14. When you do update your projects to .NET 10, which is the next LTS version of the SDK, consider how you can take advantage of these quality-of-life improvements to neaten your codebase.

Similar Posts