Just today I learned that the with expression in C# is not limited to records. It can also be used with classes, as long as the class is defined with the with expression requirements. And what are the requirements to work with the with expression?
Requirements for with expressions to work:
- The type must be a reference type (
classorrecord) or a struct. - The type must be immutable in practice: meaning, all properties you’re trying to copy/replace must be:
- init-only (
initaccessor instead ofset) - Settable through a custom clone method, if you’re using a class.
- init-only (
- The type must have a
Clonemethod accessible for the compiler to use behind the scenes. - The with expression has been supported since .NET 5 / C# 9.
Records support with expressions out of the box
For example:
public record Person(string Name, int Age);
var p1 = new Person("Alice", 30);
var p2 = p1 with { Age = 31 }; // works!
Classes require more work to support
To use with on a class, you need to:
- Mark properties with
initaccessors. - Implement a
Clone()method (or overrideMemberwiseClonebehavior).
Example:
public class Person
{
public string Name { get; init; }
public int Age { get; init; }
public Person Clone() => (Person)this.MemberwiseClone();
}
Then you can use the with expression on the class:
var p1 = new Person { Name = "Alice", Age = 30 };
var p2 = p1 with { Age = 31 }; // works!
Conclusion
Applying the with expression to class-based objects allows you write immutable classes as with similar functionality as records. Sometimes you can’t use records but still want the benefit of creating immutable objects. Let an immutable class do the work for you.