C#workingdotnet

Records and pattern matching

Immutable value types that compare by content, plus the patterns that read them apart.

record gives you value equality, with-expressions, and deconstruction for free β€” then property and positional patterns let switch branch on shape.

Records are value types by content

A record is a reference type whose equality is structural, not referential. Declare one with a positional parameter list and the compiler synthesises an immutable property per parameter, plus Equals, GetHashCode, a friendly ToString, and a Deconstruct method. Two records are equal when their runtime type and every field match β€” no IEquatable boilerplate, no overriding ==.

record Point(int X, int Y);

Because the generated properties are init-only, a record is immutable after construction. To "change" one you make a copy.

Non-destructive mutation with with

The with expression copies a record and overrides only the members you name, leaving the original untouched. This is how you evolve immutable state: var moved = p with { Y = 9 }; produces a fresh record. The copy uses the synthesised clone, so nested unchanged fields are shared, not deep-cloned.

Deconstruction and patterns

The compiler-generated Deconstruct lets you split a record into its parts: var (x, y) = p;. The same machinery powers positional patterns in switch β€” (0, 0) matches the origin. Property patterns match named members instead β€” { X: 0 } matches any point on the Y axis. Combine them with when guards, relational patterns (> 0), and or/and to express branching logic declaratively.

Try it 5 examples

Records compare by value

C#intro
0ms
TrueTrueFalseFalseTruePoint { X = 2, Y = 3 }
Reference Β· output compiled offline, not runnable in-browser

a == b and a.Equals(b) are both True because records compare by content, yet ReferenceEquals(a, b) is False β€” they are equal-by-value but distinct objects. Matching values also yield equal hash codes, and the synthesised ToString prints Point { X = 2, Y = 3 }.