Records and Pattern Matching
Immutable data types, switch expressions, and pattern matching
Introduction
In this lesson, you'll learn about records and pattern matching in C#. Coming from JavaScript, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In JavaScript, you're familiar with immutable data types, switch expressions, and pattern matching.
C# has its own approach to immutable data types, switch expressions, and pattern matching, which we'll explore step by step.
The C# Way
Let's see how C# handles this concept. Here's a typical example:
// record: immutable by default, value equality, ToString built-in
public record Point(double X, double Y);
public record Circle(double Radius) : Shape;
public record Rect(double W, double H) : Shape;
// 'with' expression — non-destructive mutation
var p1 = new Point(1, 2);
var p2 = p1 with { X = 5 }; // Point(5, 2)
// switch expression with pattern matching
string Describe(Shape shape) => shape switch
{
Circle { Radius: var r } => $"circle r={r}",
Rect { W: var w, H: var h } => $"rect {w}x{h}",
_ => "unknown"
};
// Property pattern
bool IsLarge(Shape s) => s is Circle { Radius: > 10 };
// Positional pattern (deconstruct)
bool IsOrigin(Point p) => p is (0, 0);
// List pattern (C# 11)
bool StartsWithOne(int[] arr) => arr is [1, ..];Comparing to JavaScript
Here's how you might have written similar code in JavaScript:
// Immutable-ish objects via Object.freeze
const point = Object.freeze({ x: 1, y: 2 });
// Spread for "copy with change"
const moved = { ...point, x: 5 };
// switch statement
function describe(shape) {
switch (shape.type) {
case "circle": return `circle r=${shape.radius}`;
case "rect": return `rect ${shape.w}x${shape.h}`;
default: return "unknown";
}
}
// Destructuring
const { x, y } = point;You may be used to different syntax or behavior.
record declares an immutable type with auto-generated constructor, Equals, ToString
You may be used to different syntax or behavior.
'with' expression creates a modified copy without mutation
You may be used to different syntax or behavior.
switch expression (=>) returns a value; each arm ends with ,
You may be used to different syntax or behavior.
Property patterns match on property values inside { }
You may be used to different syntax or behavior.
_ is the discard/default arm; .. is the slice pattern for collections
Step-by-Step Breakdown
1. Record Types
Records are concise immutable data classes. They auto-generate constructor, Equals (structural), GetHashCode, and ToString.
const p = Object.freeze({ x: 1, y: 2 });record Point(double X, double Y);
var p = new Point(1, 2);
Console.WriteLine(p); // Point { X = 1, Y = 2 }2. with Expression
The 'with' expression creates a shallow copy of a record with some properties changed — perfect for functional update patterns.
const updated = { ...obj, x: 5 };var updated = obj with { X = 5 };3. Switch Expressions
Switch expressions are exhaustive and return a value. Use _ for the default arm.
switch(x) { case 1: return "one"; default: return "other"; }string result = x switch {
1 => "one",
2 => "two",
_ => "other"
};4. Pattern Matching
Property patterns let you match and deconstruct in one expression. Combine with when for guards.
if (shape.type === "circle" && shape.radius > 10) ...if (shape is Circle { Radius: > 10 } c)
Console.WriteLine($"Large circle: {c.Radius}");Common Mistakes
When coming from JavaScript, developers often make these mistakes:
- record declares an immutable type with auto-generated constructor, Equals, ToString
- 'with' expression creates a modified copy without mutation
- switch expression (=>) returns a value; each arm ends with ,
Key Takeaways
- record = immutable value type with structural equality and auto-generated ToString
- 'with' expression creates a modified copy — the functional update pattern
- switch expression returns a value; each arm uses => not : and ends with ,
- Property patterns ({ Prop: pattern }) match and deconstruct in one step