C# Records vs Classes: Understanding the Difference and When to Use Each

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:

employee1
Name = Asmita
Age = 30
employee2
Name = Asmita
Age = 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:

class
record class
struct
record struct

For most API and application scenarios, the most common comparison is:

class
vs
record 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 #123
Order #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

FeatureClassRecord
Reference typeโœ…โœ… record class
Value-based equalityโŒ by defaultโœ…
Reference-based equalityโœ… by defaultโŒ
with expressionโŒโœ…
Concise syntaxNormalVery concise
Immutability supportManualBuilt-in pattern
Useful ToString()โŒ by defaultโœ…
Mutable propertiesโœ…โœ…
Good for entitiesโœ…Usually not
Good for DTOsโœ…โœ… Excellent
Good for value objectsPossibleโœ… 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 โ†’ class
Customer โ†’ class
ProductService โ†’ class
WidgetController โ†’ class
CreateUserRequest โ†’ record
UserResponse โ†’ record
Address โ†’ record
Configuration 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
โ†“
Identity
Reference equality
Behavior/state
Record
โ†“
Value
Value equality
Data/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.

Leave a comment