Records

Records are C#'s answer to "I just want a simple, immutable bundle of data". In a single line they give you a constructor, read-only properties, a readable ToString() , value-based equality and copy-and-modify with expressions — all the boilerplate you'd otherwise write by hand. They're the modern way to model data that flows through your program.

Learn Records in our free C# course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick recall.

Part of the free C# course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

Think of a printed receipt. Once it's printed, you don't scribble on it — if a price changes, the till prints a new receipt. Two receipts with identical lines are "the same" to a customer, even though they're separate pieces of paper. Records work exactly like that: they're immutable (print a new one with with to "change" it), and two records with the same contents are considered equal.

1. Declaring a Record

The positional syntax — record Person(string Name, int Age); — is the whole declaration. The compiler generates a constructor that takes Name and Age , creates matching read-only properties, and writes a tidy ToString() . That's a dozen lines of a hand-written class replaced by one.

2. Value Equality

This is the headline feature. With a normal class, a == b asks "are these the same object in memory?". With a record, it asks "do they have the same values ?". Two records built from identical data are equal, which is exactly what you want for data objects.

3. Immutability & with Expressions

Record properties are read-only after construction, which makes them safe to share and reason about. To "change" one, you don't mutate it — you create a copy with the change using a with expression. The original stays exactly as it was. This is called non-destructive mutation .

Your turn — declare a Product record and make a discounted copy with with .

4. Deconstruction & Patterns

Every positional record gets a free Deconstruct method, so you can unpack it into separate variables with var (a, b) = record; . Records also slot perfectly into the pattern matching you just learned — property patterns read straight off a record's properties.

These lines define and use a record, but they're out of order. Arrange them so the program compiles and prints the discounted book.

The record type must be declared before it's used, and you must create book before deriving sale from it. (Method braces around the body are simplified here.)

True — records use value equality; both have X=1, Y=2.

2 9 — with copies into a new record; a is unchanged.

Records shine when modelling data that flows through your app. Here a list of order records is filtered and summed with LINQ, then one is "updated" immutably with with .

Model a bank account as an immutable record and "deposit" by producing a new snapshot with with . Note how the two snapshots compare.

Practice quiz

How do two records with identical property values compare with ==?

  • They are never equal
  • It throws an exception
  • They are equal (value equality)
  • Only if they are the same object in memory

Answer: They are equal (value equality). Records compare by value: two records are equal when all their properties are equal.

What does a 'with' expression do?

  • Creates a copy with some properties changed
  • Mutates the original record in place
  • Deletes the record
  • Compares two records

Answer: Creates a copy with some properties changed. 'with' performs non-destructive mutation — it returns a new copy with the listed properties changed; the original is untouched.

What is the default mutability of positional record properties?

  • Fully mutable
  • Static
  • Private
  • init-only (read-only after construction)

Answer: init-only (read-only after construction). Positional record properties are init-only by default, which is what makes value equality and 'with' safe.

How many lines does 'record Person(string Name, int Age);' need to declare a constructor, properties, ToString and equality?

  • Ten
  • One
  • Five
  • Zero — you must write them

Answer: One. The positional syntax is the whole declaration; the compiler generates the rest.

Every positional record gets which method generated for unpacking?

  • Deconstruct
  • Dispose
  • Clone
  • GetHashSet

Answer: Deconstruct. Records auto-generate a Deconstruct method, so you can write var (a, b) = record;.

What does 'record struct Point(int X, int Y);' create?

  • A reference-type record
  • An interface
  • A value-type record copied by value
  • An abstract class

Answer: A value-type record copied by value. record struct gives a value-type record that is copied by value like a struct, while still getting generated equality and ToString.

For a normal class (not a record), what does a == b test by default?

  • Value equality
  • Reference equality (same object)
  • Property equality
  • Type equality

Answer: Reference equality (same object). Regular classes inherit reference equality — a == b is true only when both refer to the same object.

What does 'x with { ... };' do if you don't assign its result?

  • Mutates x
  • Throws CS8852
  • Deletes x
  • Nothing useful — the copy is discarded

Answer: Nothing useful — the copy is discarded. 'with' returns a copy; on its own, with no assignment, that copy is thrown away and x is unchanged.

When is a record generally preferred over a class?

  • When the object has identity and mutable behaviour
  • When it is primarily a bundle of data compared and copied by value
  • When you need a service or controller
  • Never — classes are always better

Answer: When it is primarily a bundle of data compared and copied by value. Use records for data (DTOs, events, coordinates); use classes for objects with identity and behaviour.

Why might error CS8852 ('Init-only property can only be assigned in an initializer') appear?

  • You used == on records
  • You forgot a constructor
  • You tried to mutate a record property after construction
  • You declared a record struct

Answer: You tried to mutate a record property after construction. Record properties are init-only; to 'change' one you use a with expression instead of mutating it.