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();
}
You can simplify the preceding code using the ?. operator:
customer?.Order = GetCurrentOrder();
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; }
}
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 };
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
}
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}'
""";
}
Executing code
Shows setting property values using the null-coalescing operator.
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");
}




Top comments (0)