C# · Course navigation

C# records, with-expressions, and value semantics

Create immutable data types with value equality, copy them with changes, and compare with structs and classes.

On this page
Before you start

C# classes and properties.

  • Create immutable data types with value equality, copy them with changes, and compare with structs and classes.
  • Predict the sample output and explain the state changes.
  • Complete the practice task and check the boundary cases.

The concept

A record is a type designed for data. A positional record such as Point(int X, int Y) generates properties, a constructor, value-based equality, a readable ToString, and a Deconstruct method. Two records are equal when their fields are equal, even though they are different objects, unlike plain classes where == compares references by default.

Records are immutable by design. A with-expression makes a copy with some properties changed and leaves the original untouched. A struct is a value type: assigning it copies the whole value, while assigning a class instance copies the reference, so both variables point at the same object. Choose structs for small, short-lived values and classes or records otherwise.

Code example

C#
using System;

record Point(int X, int Y);

struct Counter {
  public int Value;
}

class Box {
  public int Value;
}

class Program {
  static void Main() {
    var a = new Point(1, 2);
    var b = new Point(1, 2);
    Console.WriteLine(a == b);
    Console.WriteLine(ReferenceEquals(a, b));
    Console.WriteLine(a);

    var c = a with { Y = 5 };
    Console.WriteLine(c + " " + a);

    var (x, y) = c;
    Console.WriteLine(x + " " + y);

    var s1 = new Counter { Value = 1 };
    var s2 = s1;
    s2.Value = 99;
    var c1 = new Box { Value = 1 };
    var c2 = c1;
    c2.Value = 99;
    Console.WriteLine("struct copy: " + s1.Value + ", class alias: " + c1.Value);
  }
}
Output
True
False
Point { X = 1, Y = 2 }
Point { X = 1, Y = 5 } Point { X = 1, Y = 2 }
1 5
struct copy: 1, class alias: 99

Follow the code

Open the walkthrough to inspect the example one step at a time.

Show guided walkthrough
Guided walkthroughReady
1using System;
2
3record Point(int X, int Y);
4
5struct Counter {
6 public int Value;
7}
8
9class Box {
10 public int Value;
11}
12
13class Program {
14 static void Main() {
15 var a = new Point(1, 2);
16 var b = new Point(1, 2);
17 Console.WriteLine(a == b);
18 Console.WriteLine(ReferenceEquals(a, b));
19 Console.WriteLine(a);
20
21 var c = a with { Y = 5 };
22 Console.WriteLine(c + " " + a);
23
24 var (x, y) = c;
25 Console.WriteLine(x + " " + y);
26
27 var s1 = new Counter { Value = 1 };
28 var s2 = s1;
29 s2.Value = 99;
30 var c1 = new Box { Value = 1 };
31 var c2 = c1;
32 c2.Value = 99;
33 Console.WriteLine("struct copy: " + s1.Value + ", class alias: " + c1.Value);
34 }
35}

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
One line declares the properties, constructor, equality, ToString, and deconstruction.—
—
Different objects, equal fields, so the records are equal.—
True
They are still two separate objects.—
True
False
The generated ToString lists the properties.—
True
False
Point { X = 1, Y = 2 }
A copy with Y changed. The original record is untouched.—
True
False
Point { X = 1, Y = 2 }
c has Y = 5 and a still has Y = 2.—
True
False
Point { X = 1, Y = 2 }
Point { X = 1, Y = 5 } Point { X = 1, Y = 2 }
Deconstruct the record into two variables.—
True
False
Point { X = 1, Y = 2 }
Point { X = 1, Y = 5 } Point { X = 1, Y = 2 }
The two properties.—
True
False
Point { X = 1, Y = 2 }
Point { X = 1, Y = 5 } Point { X = 1, Y = 2 }
1 5
s2 is a copy of the struct s1. Changing it does not touch s1.—
True
False
Point { X = 1, Y = 2 }
Point { X = 1, Y = 5 } Point { X = 1, Y = 2 }
1 5
c2 refers to the same Box as c1, so c1 sees the change.—
True
False
Point { X = 1, Y = 2 }
Point { X = 1, Y = 5 } Point { X = 1, Y = 2 }
1 5
The struct was copied, the class instance was shared.—
True
False
Point { X = 1, Y = 2 }
Point { X = 1, Y = 5 } Point { X = 1, Y = 2 }
1 5
struct copy: 1, class alias: 99

Behind the scenes

How C# processes this code

C# compiles to code for the .NET runtime, which invokes the program entry point.

Cost and common mistakes

Equality compares each field, O(number of fields).

  • A record is immutable only through its init-only properties. A record with a mutable field or collection can still change.
  • Mutable structs are a source of bugs, because the copy that you modify is not the one you think it is.
  • Record equality is shallow. If a property is a List, two records are equal only if they hold the same List object.

Try it yourself

What does a == c give?

Reveal answer and reasoning

False, because the Y values (2 and 5) differ.

Further reading