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. -
withexpression – 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 |
|
|
Type name by default |
Displays property values |
|
|
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
-
Records were introduced in C# 9.0.
-
A regular
recordis a reference type;record structis a value type. -
Records provide value-based equality by default.
-
Classes use reference equality by default.
-
Records support convenient immutable-style programming, but are not automatically deeply immutable.
-
The
withexpression creates a shallow copy with selected changes. -
Records automatically generate useful members, including
ToString(), equality methods, andGetHashCode(). -
Records can contain methods, constructors, and business logic, just like classes.
-
Records are commonly used for DTOs and value objects.
-
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.