C# Records vs Regular Classes: Key Differences

This article explores C# record classes and how they fundamentally differ from traditional classes. Introduced in C# 9, records provide a concise, data-centric alternative to standard object-oriented classes. You will learn about the core mechanics of records—including value-based equality, non-destructive mutation, and built-in formatting—along with clear guidelines on when to choose a record over a standard class in your applications.


What Is a Record Class?

A record class (defined using the record or record class keyword) is a reference type designed specifically for encapsulating data rather than mutable behavior. While regular classes focus on object identity and state changes, records prioritize immutability and data consistency, significantly cutting down boilerplate code for common data structures.

Key Differences Between Records and Classes

1. Equality Semantics: Value Equality vs. Reference Equality

The most significant distinction between records and regular classes is how equality is evaluated:

  • Regular Classes: Use reference equality by default. Two distinct instances of a class are considered equal only if they point to the exact same memory address, regardless of whether their property values are identical.
  • Records: Use value equality by default. The compiler synthesizes an Equals method and overrides the == and != operators so that two separate record instances are considered equal if they are of the same type and hold identical values for all properties.
// Regular Class
public class ClassPerson
{
    public string Name { get; set; }
}

var c1 = new ClassPerson { Name = "Alice" };
var c2 = new ClassPerson { Name = "Alice" };
Console.WriteLine(c1 == c2); // False (different references)

// Record Class
public record RecordPerson(string Name);

var r1 = new RecordPerson("Alice");
var r2 = new RecordPerson("Alice");
Console.WriteLine(r1 == r2); // True (same values)

2. Concise Syntax and Immutability

Records support positional syntax, allowing you to declare properties, a primary constructor, and deconstruction logic in a single line.

  • Regular Classes: Require explicit constructors, backing fields, and property declarations.
  • Records: Properties declared in a positional record are immutable by default with init-only accessors.
// Record defined in one line
public record Book(string Title, string Author, decimal Price);

This single line automatically generates:

  • Public init-only properties for Title, Author, and Price.
  • A primary constructor to populate them.
  • Built-in deconstructors for tuple-like extraction.

3. Non-Destructive Mutation (with Expression)

Because records are commonly immutable, modifying an object usually requires creating a new copy with updated values. Records natively support non-destructive mutation via the with expression:

var original = new Book("C# in Depth", "Jon Skeet", 39.99m);
var discounted = original with { Price = 29.99m };

In a standard class, achieving this requires implementing a custom copy constructor or a cloning method.

4. Built-in ToString() Formatting

  • Regular Classes: By default, calling ToString() prints the fully qualified type name (e.g., MyApp.Models.ClassPerson).
  • Records: The compiler automatically overrides ToString() to display the record's type name and a formatted list of all its property names and values:
Console.WriteLine(original);
// Output: Book { Title = C# in Depth, Author = Jon Skeet, Price = 39.99 }

Summary of Differences

Feature Regular Class Record Class
Default Equality Reference (memory address) Value (property contents)
Primary Focus Encapsulated state and behavior Immutable data structures
Default Mutability Mutable (get; set;) Immutable (get; init;) with positional syntax
with Expression Not supported Supported natively
Generated ToString() Displays class namespace/name Displays properties and values
Deconstruction Requires manual Deconstruct method Built-in for positional records

When to Use Which

  • Choose Records for data transfer objects (DTOs), API request and response payloads, event messages, domain values, or any scenario where the identity of the object depends purely on the data it carries.
  • Choose Regular Classes for rich domain models with complex internal state, operations that require mutable behavior, or entities where object identity is distinct from object state (such as entities tracked by an ORM like Entity Framework).