Skip to content

/compare/csharp/record-vs-class

C# record vs class: equality, immutability and with expressions

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash) · 10.0.401updated
tool
C#
compares
record vs class

A C# record is a class with value-based equality: two records are equal when all their properties are equal, while two class instances are equal only when they are the same object. Records declared with positional parameters (record Point(int X, int Y)) get init-only properties, a readable ToString(), deconstruction and non-destructive copies with with. Use records for data that is compared by value (DTOs, messages, keys); use classes for objects with identity and changing state.

Side by side

bash
export DOTNET_NOLOGO=1 DOTNET_CLI_TELEMETRY_OPTOUT=1
dotnet new console -o app >/dev/null 2>&1
cd app
cat > Program.cs <<'EOF'
var r1 = new PointRecord(1, 2);
var r2 = new PointRecord(1, 2);
var c1 = new PointClass { X = 1, Y = 2 };
var c2 = new PointClass { X = 1, Y = 2 };

Console.WriteLine($"record equal: {r1 == r2}, Equals: {r1.Equals(r2)}");
Console.WriteLine($"class  equal: {c1 == c2}, Equals: {c1.Equals(c2)}");
Console.WriteLine($"record ToString: {r1}");
Console.WriteLine($"class  ToString: {c1}");

var moved = r1 with { Y = 5 };       // copy with one property changed
Console.WriteLine($"with: {moved}, original: {r1}");

var (x, y) = r1;                     // positional records deconstruct
Console.WriteLine($"deconstruct: {x} {y}");

record PointRecord(int X, int Y);
class PointClass { public int X { get; set; } public int Y { get; set; } }
EOF
dotnet run

Output

record equal: True, Equals: True
class  equal: False, Equals: False
record ToString: PointRecord { X = 1, Y = 2 }
class  ToString: PointClass
with: PointRecord { X = 1, Y = 5 }, original: PointRecord { X = 1, Y = 2 }
deconstruct: 1 2

Differences

record (record class)class
== and EqualsCompare property valuesCompare references (same object)
GetHashCodeBased on property valuesBased on identity
Positional propertiesinit-only, set once at constructionDeclared by hand, often get; set;
with expressionYes: copy with changesNo (C# 10+ allows with on structs and anonymous types)
ToString()Type name and property valuesType name only
DeconstructGenerated for positional recordsWrite it yourself
InheritanceFrom other records onlyFrom any class
MemoryReference type (heap); record struct is a value typeReference type (heap)

Which one to use

  • record: immutable data, values compared by content, API request and response models, events, dictionary keys.
  • record struct (C# 10+): small value types where you want the generated equality and ToString without heap allocations.
  • class: entities with identity (an EF Core entity, a service, a UI view model) and objects whose state changes over time.

Sources