Use a record when a type primarily carries data and two separate instances with matching values should count as equal. Use a plain class when the object has identity, mutable state, substantial behavior, or a conventional class-inheritance role. A record class keeps reference semantics; a record struct is copied by value.
Choose by the kind of equality you need
The central difference is equality. A plain class uses reference equality by default: two class instances are unequal unless they refer to the same object, or you implement another equality policy. Record types generate value equality, comparing their members; for record classes in an inheritance hierarchy, runtime type also matters. Microsoft Learn recommends records when a type’s primary role is storing data and instances with the same values should be considered equal (Microsoft Learn: C# record types; C# equality comparisons).
As an Amazon Associate I earn from qualifying purchases.
That does not mean a record class is a value type. Both a record class and a plain class are reference types: assigning one variable to another copies the reference, so both can refer to the same object. The record modifier changes generated behavior, including equality, but not that assignment rule.
Use a record class for reference-based data values
Choose record class when the object should be shared and passed around by reference, but its meaning is primarily its data and equality should reflect that data. This works well for value-like data models where creating a changed version is preferable to editing the existing instance.
#1 Best Overall
Positional record classes provide a concise constructor-and-property declaration, and their positional properties are init-only by default. Positional records can also be deconstructed. The positional syntax is optional: use ordinary property declarations when you need custom accessors, required members, or mutable properties.
public record CustomerName(string Given, string Family);
A record’s with expression creates a copy and applies selected changes to that copy; it does not edit the original instance in place.
Rank #2
var original = new CustomerName("Sam", "Lee");
var revised = original with { Family = "Chen" };
Use a record struct for small copyable values
Choose record struct for a small, self-contained value that should be copied when assigned and compared by its member values. Unlike a record class, a struct record has value-type assignment semantics. Positional properties are read/write by default; declaring it readonly record struct makes those properties init-only.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallpublic readonly record struct Point(int X, int Y);
Do not choose a struct just to obtain record equality: its value-copying behavior is a separate design decision. If the object should retain shared identity or may be large or long-lived, a class is generally the more appropriate fit. Microsoft’s guidance discusses these choices in its tutorial on choosing between tuples, records, structs, and classes.
Use a plain class for identity, changing state, or behavior
A class is the natural choice when an object represents a particular entity over time, has state that changes, or owns meaningful behavior. Since assignments copy references, different parts of a program can observe updates to the same object. Classes are also the conventional choice when a type needs a class inheritance hierarchy. Microsoft’s C# classes guidance identifies mutable state, complex behavior, shared identity, inheritance, and large or long-lived instances as class use cases.
Classes can still have immutable-looking APIs, including init-only properties. Choosing a class does not require making every property mutable; choose it when the object’s identity, lifecycle, behavior, or inheritance role matters more than generated value equality.
Rank #4
Compare the practical trade-offs
| Decision | Record class | Plain class | Record struct |
|---|---|---|---|
| Assignment | Copies a reference; variables can share one object. | Copies a reference; variables can share one object. | Copies the value’s data. |
| Default equality | Generated member-value equality; runtime type also matters in a record inheritance hierarchy. | Reference equality unless customized. | Generated value equality. |
| Positional properties | init-only by default. |
Not applicable to ordinary positional class declarations. | Read/write by default; readonly record struct makes them init-only. |
| Typical fit | Reference-based data where matching values mean equality. | Identity, mutable state, behavior, or conventional class inheritance. | Small, self-contained values that should copy by value. |
Know what records do not guarantee
Immutability is shallow, not automatic
An init-only property prevents replacing that property after initialization through ordinary assignment, but it does not freeze the object it references. If a record contains an array or another mutable object, that referenced object’s contents can still change. Record equality uses the equality behavior of member values; it does not promise recursive comparison of collection contents or a deep immutable snapshot.
Record inheritance cannot mix with ordinary class inheritance
A record class can derive from another record class. A record cannot derive from an ordinary class, and an ordinary class cannot derive from a record. If a type belongs in a conventional class hierarchy, use classes consistently across that hierarchy.
Best Value
EF Core entities are a special case
Microsoft Learn advises against using records as EF Core entity types because EF Core change tracking relies on reference equality. The C# language reference also notes that EF Core does not support updating immutable entity types (C# record types; Records language reference). This is a specific caution about entity tracking and updates, not a blanket restriction on records for DTOs, API payloads, or every persistence-related model.
When a tuple is enough
If you only need to return a few related values from one method, a tuple may be simpler than defining a named type. For a reusable type with meaningful named data and value equality, consider a record; for identity or behavior, use a class. The choice is about what the type represents and how it should behave, not a rule that all data must be declared as records.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




