← Back to Articles         
C# Records vs Classes – Detailed Explanation

C# Records vs Classes – Detailed Explanation

Published on 12 Oct 2026     7 min read C#
Modern C# Features

C# Records vs Classes – Detailed Explanation

1. What is a Record in C#?

A record is a special type introduced in C# 9.0 that is mainly used to represent data.

Records are similar to classes, but they provide additional features such as:

  • Value-based equality – Two records with the same values are considered equal.

  • Immutability support – Records make it easier to create objects whose properties cannot be reassigned after initialization.

  • Less boilerplate code – Automatically generates equality methods, ToString(), and other members.

  • with expression – Creates a copy of an object with modified values.

Important: A record is a reference type by default, just like a class. C# also supports record struct, which is a value type.

2. How Does a Record Work?

Consider an application that stores employee information.

With a traditional class, two objects containing identical data are normally considered different when using ==.

With a record, C# automatically compares their values.

                 C# Types
                     |
          +----------+----------+
          |                     |
     Reference Types        Value Types
          |                     |
       class                struct
       record               record struct
          |
     Value-based equality
     Built-in copying support
     Generated ToString()

3. Example Program – Class vs Record

using System;

public class EmployeeClass
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
}

public record EmployeeRecord(int Id, string Name);

class Program
{
    static void Main()
    {
        // Class objects
        var emp1 = new EmployeeClass
        {
            Id = 101,
            Name = "John"
        };

        var emp2 = new EmployeeClass
        {
            Id = 101,
            Name = "John"
        };

        Console.WriteLine(emp1 == emp2);

        // Record objects
        var rec1 = new EmployeeRecord(101, "John");
        var rec2 = new EmployeeRecord(101, "John");

        Console.WriteLine(rec1 == rec2);
    }
}

Output:

False
True

Explanation

Class:

emp1 == emp2

Returns false because emp1 and emp2 refer to different objects.

Classes use reference equality by default unless equality behavior is explicitly implemented.

Record:

rec1 == rec2

Returns true because both records contain identical values.

The compiler automatically generates value-based equality logic.

Class Objects
-------------
emp1 --> EmployeeClass { Id=101, Name="John" }
emp2 --> EmployeeClass { Id=101, Name="John" }

Different references
Result: False


Record Objects
--------------
rec1 --> EmployeeRecord { Id=101, Name="John" }
rec2 --> EmployeeRecord { Id=101, Name="John" }

Same values
Result: True

4. What Is Immutability in Records?

Immutability means an object's state cannot be changed after creation.

Positional record properties are init-only by default, meaning they can be assigned during initialization but cannot be reassigned afterward.

Example

public record Employee(int Id, string Name);

class Program
{
    static void Main()
    {
        Employee emp = new(101, "John");

        Console.WriteLine(emp.Name);

        // Compilation error
        // emp.Name = "David";
    }
}

Output:

John

The following statement produces a compilation error:

emp.Name = "David";

Because the Name property is init-only.

However, records are not automatically deeply immutable. You can explicitly define mutable properties, and reference-type properties can contain mutable objects.

5. What Is the with Expression?

The with expression creates a new record object by copying an existing record and changing selected properties.

Example

public record Employee(int Id, string Name);

class Program
{
    static void Main()
    {
        Employee emp1 = new(101, "John");

        Employee emp2 = emp1 with
        {
            Name = "David"
        };

        Console.WriteLine(emp1);
        Console.WriteLine(emp2);
    }
}

Output:

Employee { Id = 101, Name = John }
Employee { Id = 101, Name = David }

Explanation

Original Record
---------------
emp1
  Id   = 101
  Name = John
        |
        | with { Name = "David" }
        v
New Record
----------
emp2
  Id   = 101
  Name = David

emp1 remains unchanged.

Key point: The with expression performs a shallow copy. Nested mutable objects are not automatically cloned.

Also, with expressions are supported by records, structs, and certain other types; they are not exclusively a record feature.

6. Different Types of Records in C#

C# supports three common record declarations.

A. Positional Record

public record Employee(int Id, string Name);

Automatically generates a constructor and public init-only properties.

B. Record with Explicit Properties

public record Employee
{
    public int Id { get; init; }
    public string Name { get; init; } = "";
}

This gives you more control over the property definitions.

C. Record Struct

public record struct Employee(int Id, string Name);

Unlike a regular record, record struct is a value type.

By default, positional properties of a record struct are mutable. Use readonly record struct for an immutable-style declaration.

public readonly record struct Employee(int Id, string Name);

7. Difference Between Record and Class

Feature

Class

Record

Type

Reference type

Reference type by default

Equality

Reference equality by default

Value-based equality

Immutability

Must be designed

Convenient init-only support

ToString()

Type name by default

Displays property values

with expression

Not supported for ordinary classes

Supported

Inheritance

Supported

Supported between record classes

Constructor

Explicit or generated default

Supports positional syntax

Best suited for

Business objects, services, entities

DTOs, data models, value objects

Note: A record class cannot inherit from an ordinary class other than object, and an ordinary class cannot inherit from a record.

8. Real-World Example – ASP.NET Core Web API

Suppose an API returns employee information.

Instead of creating a traditional DTO class, you can use a record.

public record EmployeeDto(
    int Id,
    string Name,
    string Department
);

Controller:

[ApiController]
[Route("api/employees")]
public class EmployeesController : ControllerBase
{
    [HttpGet("{id}")]
    public ActionResult<EmployeeDto> GetEmployee(int id)
    {
        var employee = new EmployeeDto(
            id,
            "John",
            "IT"
        );

        return Ok(employee);
    }
}

JSON Response:

{
  "id": 101,
  "name": "John",
  "department": "IT"
}

Why use records for DTOs?

  • Less code compared with traditional DTO classes.

  • Easy to represent request and response data.

  • Supports value-based equality, useful in testing.

  • Convenient for immutable-style data transfer.

However, records are not mandatory for DTOs. Ordinary classes are also perfectly valid.

9. When Should We Use Records Instead of Classes?

Scenario

Recommended

API request/response DTO

Record or class

Value objects in DDD

Record

Immutable configuration snapshot

Record

EF Core entity with identity

Class

Service containing business logic

Class

Mutable application state

Class

Data transfer between layers

Record or class

For example, in a banking application:

// Entity with identity and lifecycle
public class BankAccount
{
    public int Id { get; set; }
    public decimal Balance { get; set; }
}

// Value object
public record Money(decimal Amount, string Currency);

BankAccount is typically a class because it represents an entity with its own identity and changing state.

Money can be a record because two money values with the same amount and currency should compare as equal.

For production code, you would normally also validate the amount and currency and define how arithmetic should work.

10. Important Key Points for Interviews

  1. Records were introduced in C# 9.0.

  2. A regular record is a reference type; record struct is a value type.

  3. Records provide value-based equality by default.

  4. Classes use reference equality by default.

  5. Records support convenient immutable-style programming, but are not automatically deeply immutable.

  6. The with expression creates a shallow copy with selected changes.

  7. Records automatically generate useful members, including ToString(), equality methods, and GetHashCode().

  8. Records can contain methods, constructors, and business logic, just like classes.

  9. Records are commonly used for DTOs and value objects.

  10. Classes are generally preferred for EF Core entities with identity and mutable state.

11. Interview Summary

Question: What are records, and how do they differ from classes?

Records are special C# types designed primarily for representing data. The main difference is that records use value-based equality, whereas classes use reference equality by default.

Records also provide built-in features such as concise positional declarations, generated ToString() implementations, and convenient copying using with.

In real-world ASP.NET Core applications, records are useful for DTOs and value objects, while classes are commonly used for entities, services, and objects with mutable state.

← Back to Articles