Skip to content
Theory · Enums, records and patterns · lesson 4 of 8

The with expression

The properties of a positional record cannot be changed after creation. But data in real life does change: an employee moves to another room, and the pass must show the new number.

Nobody corrects the old pass with a pen; a new one is printed. The name is carried over from the old card, and the new room number is written in. The old card stays as it was.

For records, the with expression works this way. It creates a new record—a copy of the original with the listed properties replaced:

C#
var anna = new Pass("Anna", 7);
var moved = anna with { Room = 12 };
Console.WriteLine(anna);  // Pass { Name = Anna, Room = 7 }
Console.WriteLine(moved); // Pass { Name = Anna, Room = 12 }
Console.WriteLine(anna == moved); // False

record Pass(string Name, int Room);

Let's take the program apart line by line:

  1. new Pass("Anna", 7) creates Anna's pass with room 7.
  2. anna with { Room = 12 } copies all the properties of anna into a new record and replaces Room there with 12. The new record goes into moved.
  3. The record anna has not changed: printing shows Room = 7.
  4. moved is a different record, with Room = 12, and the name Anna copied into it.
  5. anna == moved gives false: the records differ in one property.

The parts of with

The expression anna with { Room = 12 } has three parts:

  • on the left, the original record, here anna;
  • the word with;
  • in braces, the properties to replace, written as Room = 12.

Several replacements are separated by commas: anna with { Name = "Ann", Room = 3 }.

Properties that are not in the braces are copied as they are. The result is a value, like that of any expression: you store it in a variable or pass it on right away. The original record never changes.

The braces after with work like an object initializer from the theme on classes: they set the properties of the new copy while it is being created. That is why with can set even an init property, and it leaves the old record alone.

Besides records, with works with structs.

The copy is shallow

Interviewers often ask how deep a copy with makes. The answer: the copy is shallow. Each property is copied the same way as in an assignment: a number as a number, and a reference as a reference.

C#
var a = new Order("Anna", ["tea"]);
var b = a with { Name = "Bob" };
b.Items.Add("cake");
Console.WriteLine(a.Items.Count);      // 2
Console.WriteLine(a.Items == b.Items); // True

record Order(string Name, List<string> Items);

Let's go line by line:

  1. a holds a name and a reference to a list with one element, "tea". Here the parameter Items gives ["tea"] its type: the constructor expects a List<string>.
  2. with replaces the name in the copy, and from Items it copies only the reference, not the list itself.
  3. b.Items.Add("cake") adds an element to that single list.
  4. That is why a.Items.Count is 2: the record a is the same, but its list changed too.
  5. a.Items == b.Items gives true: both properties lead to one list.

The pass analogy needs a caveat here: the new card is printed in full, but not everything it leads to is copied. Imagine the pass carries a locker number. The new pass gets the same number, not a new locker, and the things in the locker are shared by both passes.

If the copy needs a list of its own, give it a new one: a with { Items = new List<string>(a.Items) }. The code new List<string>(a.Items) creates a separate list with the same elements.

Common mistake

Someone tried to apply with to a class object:

C#
var h = new House();
var g = h with { Floors = 3 }; // ✗

class House { public int Floors = 1; }
TEXT
error CS8858: The receiver type 'House' is not a valid record type and is not a struct type.

The program doesn't compile: you cannot apply with to a class object. For a record, the compiler writes a hidden method that makes the copy, and a class has no such method. If objects of your type are copied and compared by their data, declare it as a record, not a class.

Key points

  • r with { Room = 12 } creates a new record: a copy of r with the listed properties replaced.
  • The original record doesn't change, and properties not named in the braces are copied as they are.
  • The copy is shallow: a reference property is copied as a reference, and two records share one list.
  • with works with records and structs, but not with classes: CS8858.