DEV Community

Cover image for C# null-conditional assignment
Karen Payne
Karen Payne

Posted on

C# null-conditional assignment

Introduction

C# 14 introduces null-conditional assignment, allowing the ?. and ?[] operators to appear on the left side of an assignment. The assignment occurs only when the target object is not null; otherwise, it is safely skipped.

For example, customer?.Name = "Karen"; replaces an explicit null check and can also be used with compound assignment operators such as += and -=.

Before C# 14, you needed to null-check a variable before assigning to a property:

if (customer is not null)
{
    customer.Order = GetCurrentOrder();
}
Enter fullscreen mode Exit fullscreen mode

You can simplify the preceding code using the ?. operator:

customer?.Order = GetCurrentOrder();
Enter fullscreen mode Exit fullscreen mode

All of the above came from Microsoft documentation


💡 Before implementing in code make sure it meets all business requirements.


Example 1

Models

public class Customer
{
    public Order Order { get; set; }
}

public class Order
{
    public int Id { get; set; }
    public List<OrderItem> OrderItems { get; set; }
}

public class OrderItem
{
    public int OrderItemId { get; set; }
    public int OrderId { get; set; }
    public Order Order { get; set; }
    public int ProductId { get; set; }
    public Product? Product { get; set; }
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
}

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Executing code

First time GetCurrentOrder is assigned to customer.Order the order is assigned as we have a valid Customer while on the second attempt there is no assignment as there is no valid Customer.

private static void HandleCustomerOrderAssignment()
{

    SpectreConsoleHelpers.PrintPink();

    Customer customer = new();

    customer?.Order = GetCurrentOrder();  // order is assigned to customer.Order 
    AnsiConsole.MarkupLine($"[DeepPink1]customer!.Order.Id[/] {customer!.Order.Id}");

    customer = null;

    // Null-conditional assignment, no exception is thrown, and the assignment is skipped
    customer?.Order = GetCurrentOrder(); 
    AnsiConsole.MarkupLine("[DeepPink1]No exceptions[/]");
}

private static Order GetCurrentOrder() => new() { Id = 111 };
Enter fullscreen mode Exit fullscreen mode

results from above code

Example 2

Uses the same models as above. In this example goes down to OrderItem of an Order.

Executing code

private static void HandleOrderItems()
{
    SpectreConsoleHelpers.PrintPink();

    var order = new Order
    {
        Id = 123,
        OrderItems =
        [
            new OrderItem { OrderItemId = 1, Quantity = 2, UnitPrice = 10.50m },
            new OrderItem { OrderItemId = 2, Quantity = 1, UnitPrice = 25.00m }
        ]
    };

    // Display order with null-conditional access to OrderItems
    AnsiConsole.MarkupLine($"[DeepPink1]Order ID:[/] {order.Id}");
    AnsiConsole.MarkupLine("[DeepPink1]Order Items:[/]");
    order.OrderItems?.ToList()
        .ForEach(item =>
            AnsiConsole.MarkupLine($"     [DeepPink1]Item ID: " +
                                   $"{item.OrderItemId}, Quantity: " +
                                   $"{item.Quantity}, Unit Price: " +
                                   $"{item.UnitPrice:C}[/]"));

    Console.WriteLine();

    AnsiConsole.MarkupLine("[green bold]Order? anotherOrder = null[/]");
    // Null-conditional assignment for OrderItems
    Order? anotherOrder = null;
    anotherOrder?.OrderItems ??= []; // This will not execute as anotherOrder is null

    Console.WriteLine($"Another Order Items count: {anotherOrder?.OrderItems?.Count ?? 0}"); // Output: 0

    // Now assign an order to anotherOrder
    anotherOrder = new Order { Id = 456 };
    anotherOrder.OrderItems ??= new List<OrderItem>(); // This will assign a new list
    anotherOrder.OrderItems.Add(new OrderItem { OrderItemId = 3, Quantity = 5, UnitPrice = 5.00m });

    Console.WriteLine($"Another Order ID: {anotherOrder.Id}");
    AnsiConsole.WriteLine("Another Order Items:");
    anotherOrder.OrderItems.ForEach(item =>
        AnsiConsole.MarkupLine($" - Item ID: {item.OrderItemId}, " +
                               $"Quantity: {item.Quantity}, Unit Price: {item.UnitPrice:C}"));

    Console.WriteLine();

    // Example with a null order and null-conditional member access
    Order? nullableOrder = null;
    Console.WriteLine($"Nullable Order Items count: " +
                      $"{nullableOrder?.OrderItems?.Count ?? 0}"); // Output: 0

    // Null-conditional assignment on a null object, does nothing
    nullableOrder?.OrderItems ??=
        [
            new OrderItem { OrderItemId = 4, Quantity = 1, UnitPrice = 99.99m }
        ];
    Console.WriteLine($"Nullable Order Items count after assignment attempt: " +
                      $"{nullableOrder?.OrderItems?.Count ?? 0}"); // Output: 0
}
Enter fullscreen mode Exit fullscreen mode

results from above code

Example 3

Model

public sealed class Account
{
    public int Id { get; set; }
    public decimal Balance { get; set; }
    public int RewardPoints { get; set; }
    public int Flags { get; set; }
    public string? DisplayName { get; set; }

    public override string ToString() =>
        $"""
         [DeepPink1]Id:[/] {Id}, 
         [DeepPink1]Balance:[/] '{Balance:C}', 
         [DeepPink1]RewardPoints:[/] '{RewardPoints}', 
         [DeepPink1]Flags:[/] '{Flags}', 
         [DeepPink1]DisplayName:[/] '{DisplayName}'
         """;
}
Enter fullscreen mode Exit fullscreen mode

Executing code

Shows setting property values using the null-coalescing operator.

null-coalescing operators

private static void HandleAccountAssignment()
{

    SpectreConsoleHelpers.PrintPink();

    Account? account = new()
    {
        Id = 1,
        Balance = 1_000.00m,
        RewardPoints = 100,
        Flags = 0b_0011,
        DisplayName = null
    };

    account?.Balance += 100m;
    account?.Balance -= 50m;
    account?.RewardPoints %= 10;
    account?.Flags >>= 1;

    Console.WriteLine();
    AnsiConsole.MarkupLine(account?.ToString() ?? "No account");
    Console.WriteLine();

    // Null-conditional assignment
    account?.DisplayName ??= "Unknown display name";
    Console.WriteLine($"Name: {account?.DisplayName}");

    // null out the account reference
    account = null;
    // Null-conditional assignment, no exception is thrown, and the assignment is skipped
    account?.DisplayName ??= "Default display name"; 
    Console.WriteLine("No exceptions");

    account?.Balance += 100m;
    account?.Balance -= 50m;
    account?.Flags >>= 1 ;

    Console.WriteLine();
    AnsiConsole.MarkupLine(account?.ToString() ?? "No account");

}
Enter fullscreen mode Exit fullscreen mode

results from above code


Feature specification

Details

Source code

Source code

Top comments (0)