C# and .NET path · The modern language · Topic 8 of 14

Records and pattern matching

Not started

A record is a class (or, with record struct, a struct) for which the compiler writes the boilerplate of a data type: a constructor and properties from a positional declaration, value-based Equals, == and GetHashCode, a readable ToString, a copy constructor for with, and a Deconstruct method. Pattern matching is the other half: a way to ask what shape a value has and pull its parts out in the same expression.

Why it matters: Records and patterns let you model data and the decisions on it in a few lines, with the compiler checking you handled every case.

  1. 1Value equality

    Two records with equal properties are equal (==, Equals, GetHashCode), even though they are different objects.

  2. 2Immutable by default

    Positional records have init-only properties. Use a with expression to make a modified copy.

  3. 3Class or struct

    record (or record class) is a reference type; record struct is a value type with the same conveniences.

  4. 4Patterns test shape

    Type, property, positional, relational, list and logical patterns combine in switch expressions, with a warning if a case is missed.

1

How it works

The explanation, with examples you can run.

Records

var a = new Money(10m, "EUR");var b = new Money(10m, "EUR");Console.WriteLine(a == b);                 // True: same valuesConsole.WriteLine(ReferenceEquals(a, b));  // False: two objectsvar c = a with { Amount = 25m };Console.WriteLine(c);                      // Money { Amount = 25, Currency = EUR } record Money(decimal Amount, string Currency);

The positional properties are init-only, so a record is immutable unless you add settable properties yourself. That makes records good keys in dictionaries and good messages, DTOs and value objects. Two caveats: equality compares each property with its own Equals, so a record holding a List<T> compares the lists by reference; and a mutable property that changes after the record is used as a dictionary key breaks the lookup.

record struct gives the same features to a value type. A plain record struct has settable properties; readonly record struct is the immutable version.

Switch expressions and patterns

static string Describe(Shape s) => s switch{    Circle { Radius: <= 0 } => "invalid circle",    Circle c => $"circle, area {Math.PI * c.Radius * c.Radius:F2}",    Rect(var w, var h) when w == h => $"square {w}",    Rect(var w, var h) => $"rect {w}x{h}",    _ => "unknown",}; static string Classify(int[] xs) => xs switch{    [] => "empty",    [var only] => $"one: {only}",    [var first, .., var last] => $"from {first} to {last}",}; abstract record Shape;record Circle(double Radius) : Shape;record Rect(double W, double H) : Shape;

With Circle(1), Rect(2, 2), Rect(2, 3) and [1, 2, 3], this printed circle, area 3.14, square 2, rect 2x3 and from 1 to 3 on .NET 10. The patterns used:

  • type pattern Circle c: matches and binds a typed variable;
  • property pattern { Radius: <= 0 } with a relational pattern inside;
  • positional pattern Rect(var w, var h), which calls the generated Deconstruct;
  • a case guard with when;
  • list patterns (C# 11) with .. for "any number of elements";
  • logical patterns and, or and not, as in is not null or >= 0 and < 10.

Arms are tried in order, and the compiler reports an error if an arm can never be reached because an earlier one covers it, and a warning (CS8509) if some input matches no arm. That warning is the reason to prefer a switch expression over a chain of if statements: add a new kind of input and the compiler shows you every place that doesn't handle it yet. C# 14 can't declare that a hierarchy is closed, so a _ arm is still needed for an abstract base like Shape.

2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.What does this print?
    var a = new Money(10m, "EUR");var b = new Money(10m, "EUR");Console.WriteLine($"{a == b} {ReferenceEquals(a, b)}");record Money(decimal Amount, string Currency);
  2. 2.What does `a with { Amount = 25m }` do?
  3. 3.Which value matches the pattern `[var first, .., var last]`?

0 of 3 right so far. Get all three to finish this topic.