Records & Pattern Matching
7 questions foundA record is a reference type with compiler-generated value equality, ToString and non-destructive mutation (with). It's ideal for immutable data.
public record Person(string Name, int Age);
var p2 = p1 with { Age = 31 };
Real-world example DTOs and value objects use records so equality and copying just work.
with creates a copy of a record with some properties changed, leaving the original unchanged (non-destructive mutation).
var updated = order with { Status = "Shipped" };
Real-world example Producing a new immutable state object from the previous one in a reducer/state store.
A record struct is a value type with record-like value semantics and equality; use it for small immutable value data where you also want stack/value copying.
public readonly record struct Point(int X, int Y);
Real-world example Coordinates or money where value equality plus no heap allocation is desirable.
switch expressions match on type, constant, relational and property patterns, returning a value concisely and forcing exhaustive, readable branching.
string Size(int n) => n switch { < 10 => "small", < 100 => "medium", _ => "large" };
Real-world example Mapping an HTTP status or an enum to a message without long if/else chains.
They deconstruct and test an object's shape in one expression, enabling clear rules over complex data without manual null checks and casts.
var label = point switch { (0,0) => "origin", ( >0, >0) => "Q1", _ => "other" };
Real-world example Routing a domain event by its type and fields in one expressive switch.
For entities with identity and mutable state (like EF Core aggregates), where value equality and immutability are wrong; use classes there. Records suit values, not entities.
public class Order { public int Id { get; set; } public string Status { get; set; } }
Real-world example An EF Core entity tracked by Id stays a class; a money value object becomes a record.
A record is a reference type purpose-built for representing immutable data, distinguished from an ordinary class by using value-based equality by default (two records with identical property values are considered equal, even as separate instances) rather than the reference-based equality classes use -- records also support the 'with' expression for producing a modified copy without mutating the original. Structs, by contrast, are value types living on the stack, best suited to small, lightweight data that doesn't need inheritance; classes remain the right choice for mutable objects with real behavior.
public record Person(string Name, int Age);
var p1 = new Person("Alice", 30);
var p2 = new Person("Alice", 30);
Console.WriteLine(p1 == p2); // True -- value-based equality, unlike an equivalent class
var older = p1 with { Age = 31 }; // non-destructive copy
Console.WriteLine(p1.Age); // 30, unchanged
Console.WriteLine(older.Age); // 31
Real-world example An API returning immutable response DTOs models them as records, getting automatic, correct value-based equality (useful in unit tests comparing expected vs actual responses) and the 'with' expression for building slightly-modified test fixtures, without hand-writing Equals()/GetHashCode() overrides.
Common follow-ups: Why does a record's default equality behavior sometimes surprise developers used to classes?;When would a struct actually outperform an equivalent record for the same small piece of data?
Records & Sealed Classes;equals() hashCode() & toString() Contracts