Reading — step 1 of 6
Learn
C# 9 added records for concise immutable types, and C# 8-10 expanded pattern matching into a full language feature. Together they enable a functional style of data modeling.
Records
public record Point(int X, int Y);
var p1 = new Point(3, 4);
var p2 = new Point(3, 4);
Console.WriteLine(p1 == p2); // True (structural equality)
Console.WriteLine(p1); // Point { X = 3, Y = 4 }
var moved = p1 with { X = 10 }; // copy with one property changed
A single line replaces 30+ lines of equivalent immutable class boilerplate. Records auto-generate:
- Constructor matching the parameters
- Properties (init-only, can't be reassigned)
Equals,GetHashCode,ToString- A
Deconstructmethod for destructuring - The
withexpression for copy-and-modify
Record class vs record struct (C# 10+)
public record class User(string Name, int Age); // reference type (default)
public record struct Vec3(float X, float Y, float Z); // value type
record struct is great for small immutable data that doesn't need heap allocation.
Pattern matching with switch expressions
string Describe(object o) => o switch {
null => "nothing",
int n when n < 0 => $"negative {n}",
int n => $"positive {n}",
string s when s.Length > 10 => $"long string",
string s => $"short: {s}",
Point(0, 0) => "origin",
Point(var x, var y) => $"({x}, {y})",
_ => "unknown"
};
Patterns:
- Type pattern —
int n(matches if cast succeeds, binds to n) - Constant pattern —
0,null,"hi" - Property pattern —
{ Length: > 10 },{ X: 0, Y: 0 } - Positional pattern —
Point(var x, var y)(uses Deconstruct) - Var pattern —
var anything(always matches, binds) - Discard —
_ - Logical patterns (C# 9+) —
not null,> 0 and < 100,"yes" or "y"
Property patterns
bool IsAdult(Person p) => p is { Age: >= 18 };
bool IsAdminEditor(User u) => u is { Role: "admin" or "editor" };
Switch expression with records
public record Shape;
public record Circle(double Radius) : Shape;
public record Square(double Side) : Shape;
public record Rect(double Width, double Height) : Shape;
double Area(Shape s) => s switch {
Circle(var r) => Math.PI * r * r,
Square(var side) => side * side,
Rect(var w, var h) => w * h,
};
Note: when the switch is exhaustive, the compiler doesn't require a default. With non-sealed hierarchies it warns about non-exhaustive matches.
On this platform's grader (C# 7)
Records and switch expressions are C# 8/9 features — our grader runs Mono's compiler, which doesn't accept them (it supports is type patterns, but not pattern switch). The graded exercise therefore uses the is pattern, which expresses the same dispatch:
static double Area(Shape s)
{
if (s is Circle c) return Math.PI * c.Radius * c.Radius;
if (s is Square sq) return sq.Side * sq.Side;
return 0;
}
Same pattern-matching idea — s is Circle c tests the runtime type AND binds c in one step. The switch-expression form you learned above is what you'll write in modern codebases.
Common mistakes
- Trying to mutate a record property — they're init-only. Use
withto create a modified copy. - Thinking record class == means reference equality — records OVERRIDE == for structural equality. Different from regular classes.
- Pattern-matching on records without using Deconstruct —
r is Point p && p.X == 0works butr is Point(0, var y)is cleaner.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…