C# provides several ways to model data, but two of the most commonly used are classes and records. Although both can represent objects and contain properties, they are designed with different goals in mind.
The key distinction is simple:
Classes are generally used to model objects with identity and behavior, while records are designed primarily to represent data and values.
Understanding this distinction helps you choose the right type when building .NET applications, APIs, DTOs, and domain models.
1. What is a Class in C#?
A class is the traditional way of defining an object in C#.
For example:
public class Employee{ public string Name { get; set; } public int Age { get; set; } public void Promote() { // Business logic }}
We can create instances of the class:
var employee = new Employee{ Name = "Asmita", Age = 30};
A class can contain:
- Properties
- Fields
- Methods
- Constructors
- Events
- Business logic
- Interfaces
- Nested types
Classes are therefore well suited to objects that have state, behavior, and identity.
2. What is a Record?
Records were introduced in C# 9 to make it easier to work with data-centric types.
A simple record can be written as:
public record Employee(string Name, int Age);
Creating an instance is straightforward:
var employee = new Employee("Asmita", 30);
Records are particularly useful when the main purpose of the type is to carry data rather than encapsulate complex behavior.
For example:
public record Address( string Street, string City, string PostCode);
An Address is usually thought of as a value rather than an entity with its own identity.
3. The Biggest Difference: Equality
One of the most important differences between classes and records is equality.
Consider this class:
public class Employee{ public string Name { get; set; } public int Age { get; set; }}
Now create two objects:
var employee1 = new Employee{ Name = "Asmita", Age = 30};var employee2 = new Employee{ Name = "Asmita", Age = 30};
If we compare them:
Console.WriteLine(employee1 == employee2);
The result is:
False
Why?
Because classes use reference equality by default.
Although both objects contain the same data, they are two different object references.
Conceptually:
employee1 โโ> Employee { Name = "Asmita", Age = 30 }employee2 โโ> Employee { Name = "Asmita", Age = 30 }
They contain the same values but represent different object instances.
4. Records Use Value-Based Equality
Now consider the equivalent record:
public record Employee(string Name, int Age);
Create two instances:
var employee1 = new Employee("Asmita", 30);var employee2 = new Employee("Asmita", 30);
Now:
Console.WriteLine(employee1 == employee2);
returns:
True
Records use value-based equality.
In other words, C# considers the contents of the record when determining equality.
Employee("Asmita", 30) ==Employee("Asmita", 30) True
This makes records especially useful for DTOs and other data-transfer types.
5. with Expressions
Another useful feature of records is the with expression.
Suppose we have:
public record Employee(string Name, int Age);
Create an employee:
var employee1 = new Employee("Asmita", 30);
We can create a modified copy:
var employee2 = employee1 with{ Age = 31};
Now:
employee1Name = AsmitaAge = 30employee2Name = AsmitaAge = 31
The original record isn’t modified.
This is useful when working with immutable data.
6. Records and Immutability
Records are commonly associated with immutable objects.
For example:
public record Employee(string Name, int Age);
The positional properties are normally init-only, meaning they can be assigned during object initialization but aren’t normally changed afterward.
For example:
var employee = new Employee("Asmita", 30);
This won’t normally be allowed:
employee.Age = 31;
Instead, use:
var updatedEmployee = employee with{ Age = 31};
This approach can make applications easier to reason about because objects aren’t unexpectedly modified.
However, an important point is that records aren’t automatically immutable.
You can still write:
public record Employee{ public string Name { get; set; } public int Age { get; set; }}
The properties in this example are mutable.
So:
Record and immutable are related concepts, but they are not the same thing.
7. record class and record struct
C# supports different types of records.
Record class
public record Employee(string Name, int Age);
This is equivalent to:
public record class Employee(string Name, int Age);
A record class is a reference type.
Record struct
You can also create a record struct:
public record struct Point(int X, int Y);
A record struct is a value type.
So there are effectively several choices:
classrecord classstructrecord struct
For most API and application scenarios, the most common comparison is:
classvsrecord class
8. ToString() Difference
Records also provide a more useful default ToString() implementation.
For a class:
public class Employee{ public string Name { get; set; } public int Age { get; set; }}
Calling:
Console.WriteLine(employee);
typically produces something similar to:
MyApplication.Employee
A record:
public record Employee(string Name, int Age);
provides a representation containing the values, such as:
Employee { Name = Asmita, Age = 30 }
This can be useful when debugging and logging.
9. Records in ASP.NET Core APIs
Records are particularly common in modern ASP.NET Core applications.
For example, consider an API request:
public record CreateUserRequest( string Name, string Email, int Age);
A controller might receive it:
[HttpPost]public IActionResult Create(CreateUserRequest request){ // Process request return Ok();}
The request object is primarily carrying data from the client to the server.
A record is therefore a natural fit.
Similarly, an API response can be represented as:
public record UserResponse( Guid Id, string Name, string Email);
10. DTOs: A Good Use Case for Records
DTO stands for Data Transfer Object.
DTOs generally exist to move data between different parts of an application.
For example:
public record ProductDto( Guid Id, string Name, decimal Price);
A DTO generally doesn’t need a lot of behavior.
Therefore:
DTO โ Record
is often a good choice.
For example:
public record GetFolderResponse( string Id, string Name);
This clearly communicates:
“This type is primarily a piece of data.”
11. When Should You Use a Class?
Classes are generally a better choice when an object has identity, lifecycle, mutable state, or significant behavior.
For example:
public class Order{ public Guid Id { get; set; } public decimal Total { get; private set; } public void AddItem(decimal price) { Total += price; }}
An order has an identity:
Order ID = 123
It also has behavior:
order.AddItem(100);
And its state can change over its lifetime.
A class is therefore a natural choice.
12. Entity vs Value Object
A useful way to decide between a class and a record is to ask:
Does this object have identity?
If yes, a class is usually appropriate.
For example:
Order #123Order #456
Even if the two orders happen to contain exactly the same information, they are still different orders.
Does this object represent a value?
If yes, a record is often appropriate.
For example:
Address( "10 Main Street", "London", "SW1A")
Two address objects containing the same values can logically represent the same address.
13. Comparison Table
| Feature | Class | Record |
|---|---|---|
| Reference type | โ | โ
record class |
| Value-based equality | โ by default | โ |
| Reference-based equality | โ by default | โ |
with expression | โ | โ |
| Concise syntax | Normal | Very concise |
| Immutability support | Manual | Built-in pattern |
Useful ToString() | โ by default | โ |
| Mutable properties | โ | โ |
| Good for entities | โ | Usually not |
| Good for DTOs | โ | โ Excellent |
| Good for value objects | Possible | โ Excellent |
| Complex business behavior | โ | Possible, but class often clearer |
14. Example: Choosing Between Them
Suppose you’re building an e-commerce system.
Customer
public class Customer{ public Guid Id { get; set; } public string Name { get; set; } public void ChangeName(string name) { Name = name; }}
A customer has an identity and lifecycle.
Class makes sense.
Address
public record Address( string Street, string City, string PostCode);
An address primarily represents a collection of values.
Record makes sense.
API Response
public record ProductResponse( Guid Id, string Name, decimal Price);
The response primarily transfers data.
Record makes sense.
Service
public class ProductService{ public async Task<Product> GetProduct(Guid id) { // Business logic }}
A service contains behavior and dependencies.
Class makes sense.
15. A Simple Rule to Remember
When deciding between the two, ask:
“Am I modelling an entity, or am I modelling data?”
If you’re modelling an entity with identity and behavior:
class
If you’re modelling data/value:
record
For example:
Order โ classCustomer โ classProductService โ classWidgetController โ classCreateUserRequest โ recordUserResponse โ recordAddress โ recordConfiguration DTO โ record
Conclusion
Records and classes aren’t competing replacements for each other. They solve slightly different problems.
A class is usually the better choice when an object has identity, mutable state, lifecycle, or substantial business behavior.
A record is usually the better choice when the object primarily represents data and value equality is desirable.
The most important difference to remember is:
Class โIdentityReference equalityBehavior/stateRecord โValueValue equalityData/immutability
For modern .NET applications, especially ASP.NET Core APIs, records are an excellent choice for request/response DTOs, while classes remain the natural choice for services, controllers, and domain entities.
