With expressions arrived in C# 9.0 as part of the records story. They give you a simple way to create a new value from an existing one without mutating the original instance.
Why it matters
With expressions make immutable workflows feel natural. You can keep objects read-only by default while still evolving state in tiny, obvious steps.
Practical usage
Reach for with expressions when you model requests, commands, DTOs, or UI state snapshots. They pair especially well with records and make change intent easy to review.
Cautions
With expressions create shallow copies. If a property points to mutable reference data, both instances can still share the same underlying object unless you clone that data explicitly.
Clone and update immutable records
Use with expressions to copy a record and change only the fields that need to move forward.
Valid since C# 9.0
using System;
public record DeploymentConfig(string Service, string Region, int Replicas, bool DryRun);
public static class Program
{
public static void Main()
{
var baseline = new DeploymentConfig("orders-api", "eastus", 2, DryRun: true);
var productionPlan = baseline with
{
Replicas = 4,
DryRun = false
};
Console.WriteLine($"Baseline: {baseline}");
Console.WriteLine($"Production: {productionPlan}");
}
}
Represent state transitions without mutation
Model workflow steps as clear transitions by producing new values instead of mutating existing instances.
Valid since C# 9.0
using System;
public record TicketStatus(string Id, string Stage, string AssignedTo, DateTime UpdatedUtc);
public static class Program
{
public static void Main()
{
var opened = new TicketStatus("T-1024", "Open", "unassigned", DateTime.UtcNow);
var triaged = opened with { Stage = "Triaged", AssignedTo = "kat" };
var inProgress = triaged with { Stage = "In Progress", UpdatedUtc = DateTime.UtcNow };
Console.WriteLine(opened);
Console.WriteLine(triaged);
Console.WriteLine(inProgress);
}
}