Records and Value Equality
How C#'s record type gives you value-based equality and immutability with almost no boilerplate.
2 min read
By default, classes in C# compare by reference — two objects are only "equal" if they're the exact same object in memory, even if every field matches. Records flip that default to compare by value, which is usually what you actually want for data-holding types.
The problem with classes
class Point
{
public int X { get; set; }
public int Y { get; set; }
}
var p1 = new Point { X = 1, Y = 2 };
var p2 = new Point { X = 1, Y = 2 };
Console.WriteLine(p1 == p2); // false -- different objects, even though the data matchesThat's surprising if you're thinking of a Point as "just data." Fixing it with a class means overriding Equals and GetHashCode by hand — tedious, easy to get subtly wrong.
Records solve this directly
record Point(int X, int Y);
var p1 = new Point(1, 2);
var p2 = new Point(1, 2);
Console.WriteLine(p1 == p2); // true -- same values, considered equalOne line. record Point(int X, int Y) — using the same positional-parameter syntax as a primary constructor — generates a class with X and Y properties, value-based Equals/GetHashCode, and a readable ToString() (Point { X = 1, Y = 2 }) all for free.
Records are immutable by default
Positional record properties are init-only, meaning you can't reassign them after construction:
var p1 = new Point(1, 2);
p1.X = 5; // compile error: init-only propertyThis pairs naturally with value equality — it's much easier to reason about "are these two things equal" when neither one can silently change after you've compared them.
Non-destructive mutation with with
To get a modified copy of a record without mutating the original, use with:
var p1 = new Point(1, 2);
var p2 = p1 with { Y = 5 };
Console.WriteLine(p1); // Point { X = 1, Y = 2 } -- unchanged
Console.WriteLine(p2); // Point { X = 1, Y = 5 }with copies every property from p1 except the ones you explicitly override, producing a brand-new record. This is the idiomatic way to "change" an immutable record.
record vs. record struct
Plain record is a reference type (like class); record struct (C# 10+) is a value type (like struct), also with value-based equality. For small, short-lived data — coordinates, RGB colors — record struct avoids the heap allocation a reference type needs. Default to record unless you have a specific reason (measured performance, or genuinely value-type semantics like a mutable local coordinate) to reach for record struct.
Test what you just learned
4 quick questions. Get all of them right to unlock the next lesson.
You can take the quiz without an account — logging in just lets your result count toward your progress.