Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

Use a C# record when a type mainly stores data and separate instances with matching values should compare as equal. Use a plain class when the object has identity, mutable state, substantial behavior, or a conventional class-inheritance role. For small, self-contained values that should copy by value, consider a record struct.

Records vs. classes at a glance

Decision Record Plain class
Equality by default Generated value equality compares members; for record classes in an inheritance hierarchy, runtime types must also match. Reference equality: two variables compare equal by default when they refer to the same object.
Assignment record class copies a reference; record struct copies its value. Copies a reference, so aliases refer to the same object.
Mutation Positional record-class properties are init-only by default, but records may have mutable members and are not deeply immutable. Mutable state is a natural fit, though classes can also expose init-only properties.
Inheritance Record classes may inherit from record classes. Record and non-record class inheritance cannot be mixed. Supports conventional class inheritance.
Strong fit Data-centric types where value equality and copy-with-changes are useful. Objects with identity, evolving state, behavior, or class-hierarchy needs.

Microsoft Learn describes the central record use case this way: “Use records when a type’s primary role is storing data and two instances with the same values should be considered equal.”

Choose a record when matching data should mean equality

Two separately created record instances with equal member values compare as equal. This is useful when the data itself defines what an instance means, rather than the particular object’s identity. A record class is still a reference type: assigning it to another variable copies the reference, not the object. Its value-based equality does not change how it is stored or assigned.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Records also provide a with expression for creating a copy with selected changes. This is nondestructive mutation: the expression produces a new record and leaves the original unchanged.

public record Person(string FirstName, string LastName);

var original = new Person("Ada", "Lovelace");
var updated = original with { LastName = "Byron" };

Positional syntax is optional. It conveniently declares constructor parameters and corresponding properties, and positional records support deconstruction. Use ordinary property syntax when you need custom accessors, required members, or mutable properties.

Choose a class when identity, state, or behavior leads

A plain class is the better default when an object represents a distinct entity, changes over its lifetime, coordinates behavior, or belongs in a conventional class hierarchy. Class assignment copies a reference: if two variables point to the same instance, a mutation through either variable is visible through the other.

Microsoft Learn identifies complex behavior, mutable state, inheritance, shared identity, and large or long-lived instances as class use cases. These are design defaults, not language restrictions: records can contain methods and mutable members, and classes can be designed with init-only properties.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Decide whether a value type is more appropriate

Use record struct for small, self-contained data that should copy by value and compare by value. Assigning a struct record copies its data. Positional properties are read/write by default; declaring a readonly record struct makes them init-only.

For a method that simply returns a few related values, a tuple may be sufficient. Microsoft’s guide to choosing among tuples, records, structs, and classes treats tuples as a practical option for that narrow case, rather than a replacement for every data type.

Know what record equality and immutability do not guarantee

Reference-valued members are not deep snapshots

Record value equality does not promise recursive comparison of nested objects or collection contents. Member types determine their own equality behavior. For example, an array property does not become a content-compared, immutable snapshot just because it belongs to a record. An init-only reference property prevents replacing the reference after initialization, but the referenced object may remain mutable.

Records and classes have different inheritance rules

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 the design depends on a conventional class hierarchy, use classes throughout that hierarchy.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Use special care with EF Core entities

Microsoft Learn advises against using record types for EF Core entity types because EF Core change tracking relies on reference equality. Its C# language reference also notes that EF Core does not support updating with immutable entity types. This is a specific caution about entity tracking and updates—not a general ban on records for DTOs, API payloads, or every persistence-related type.

A practical decision path

  1. Ask what makes two instances the same. If matching member values should make independent instances equal, start with a record. If identity matters, start with a class.
  2. Check assignment and mutation needs. Choose a record class for reference semantics with value equality; choose a record struct when a small value should copy by value. Choose a class when aliases should share an evolving object.
  3. Check inheritance and framework behavior. Use the compatible type family for the intended hierarchy, and avoid record entities where EF Core’s reference-based tracking is required.
  4. Review nested members. If callers need deep immutability or collection-content equality, design those member types explicitly; the record declaration alone does not provide either.

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.