Records & Pattern Matching
csharp1-4 previewed init-only properties as the mechanism this chapter builds on. This is where that promise pays off in full — real record syntax, and a capability neither Java's nor even Kotlin's own records quite match.
record — Full Syntax
A one-line positional record generates even more than csharp1-4's own hand-assembled init properties: a constructor, get; init; properties for each component, correct Equals()/GetHashCode()/ToString(), and a Deconstruct method enabling tuple-style unpacking — all from one line.
Genuinely Comparable to java2-7's Records — But Arriving First
C# 9 (2020) shipped record; java2-7's own Java records arrived in Java 16 (2021) — a rare case on this site where C# genuinely got there first, worth naming plainly given how often the comparison runs the other way. Both generate equality, hashing, and string formatting automatically. C#'s positional syntax is even more compact than Java's own record header syntax, which still requires listing each component explicitly in a similar but slightly more verbose form.
with-Expressions — Non-Destructive Mutation
Here's the real reveal: with creates a brand-new record instance with just the named properties changed, copying everything else — something java2-7's own Java records have no built-in equivalent for at all. Kotlin's data class is the actual match here, via its own copy() method — a case where C# converges with Kotlin rather than with Java.
record class vs. record struct
C# 10 let a record be either a reference type (record class, the plain record keyword's default) or a genuine value type (record struct) — layering the record's free boilerplate directly on top of csharp1-2's own struct/class split. This is capability neither Java's nor Kotlin's records offer at all, since neither language has C#'s own value/reference type distinction to apply it to in the first place.
Pattern Matching, Building on csharp1-3's Light Touch
Property patterns ({ X: 0, Y: 0 }), positional patterns (using the record's own Deconstruct directly), and the and/or/not combinators are a genuinely rich pattern language — well beyond java1-3's own simpler type-pattern-plus-switch-expression combination, and a natural extension of the type patterns java1-3 already introduced.
| Aspect | Java records (java2-7) | Kotlin data class | C# record |
|---|---|---|---|
| Arrival | Java 16 (2021) | Kotlin 1.0 | C# 9 (2020) — first vs. Java |
| Non-destructive copy with one field changed | no built-in equivalent | copy() method | with { ... } expression |
| Value-type option | not applicable — Java has no value/reference split | not applicable | record struct (C# 10) |
p1 with { X = 99 } reads directly as "the same record, except X" — cleaner and less error-prone than manually re-listing every unchanged property in a fresh constructor call.
List<T>, for instance — shares that exact same list between the original and the with-modified copy. Mutating the list through either instance affects both, since with only copies the reference, not the referenced object itself.
Coding Challenges
Define a positional record Product(string Name, decimal Price), create an instance, print its ToString() output, and deconstruct it into two separate variables.
📄 View solutionUsing Challenge 1's Product record, create an instance, use a with-expression to produce a second instance with only the Price changed, and print both to show the original is unaffected.
📄 View solutionDefine a record Point(int X, int Y) and write a switch expression using a property pattern for the origin, a positional pattern for points on the X axis, and a discard for everything else. Test it against three different points.
📄 View solutionChapter 5 Quick Reference
- A one-line positional record generates a constructor, get;init; properties, Equals/GetHashCode/ToString, and Deconstruct
- C# records (C# 9, 2020) genuinely shipped before java2-7's own Java 16 (2021) records — a rare "C# first" case
- with-expressions create a modified copy with named properties changed — no Java record equivalent, but a real match with Kotlin's data class copy()
- record struct (C# 10) makes a record a value type — capability neither Java nor Kotlin records offer, since neither has C#'s value/reference split
- Property, positional, and and/or/not pattern combinators extend java1-3's own simpler type patterns considerably
- with is a shallow copy — mutable reference fields are shared between original and copy
- Next chapter: nullable reference types — a three-way comparison against Kotlin's built-in null safety and Java's total lack of either