Skip to content
  • English Deutsch

benjamin-abt.com

Performance is a feature | Cloud - AI - Azure - .NET - Sustainability

  • Home
  • Blog
  • Portfolio
    • Overview
    • Workshops & Training
    • Publications
  • Contact
  • Home
  • Blog

When to use records in C#

When to use records in C#
February 24, 2025 6 min read BEN ABT
Csharp .NET

For several years now, C# has supported Records as a new “type”. Initially only for classes (which is why the class is optional for a record class), later also for structs. Records are specially designed for use as data containers aka DTOs (please dont use DTO in your class name!). They are immutable and have special semantics for equality.

Record Classes

Usually you create a record with a primary constructor that defines the properties. Records are immutable, i.e. all values included in the immunity cannot be changed.

C#
public record class Person(string FirstName, string LastName);

The compiler creates a class with “Positional Properties” accordingly. They are immutable.

C#
1public record class Person
2{
3    public required string FirstName { get; init; }
4    public required string LastName { get; init; }
5}

However, a record can also be defined with properties that are mutable.

C#
1public record class Person
2{
3    public required string FirstName { get; init; }
4    public required string LastName { get; init; }
5
6    public required int Age { get; set; }
7}

Record Structs

Structs are value types and are often used for small data structures. Records are particularly useful here, as they are immutable and support equality semantics, which is usually the case with structs. This is achieved by the additional readonly.

C#
public readonly record struct Person(string FirstName, string LastName);

If you leave out the readonly, almost all the advantages of a struct are gone and it is ultimately a mutable struct.

C#
public record struct Person(string FirstName, string LastName);

Immutability

The basic idea of records is data structures and their immutability. This means that the values cannot be changed after initialization. All properties that are part of the immutability are included in the value equality - mutable properties are not.

Usage

Records are great for many data scenarios, but not for all. For example, records cannot be used with the Entity Framework today because the Entity Framework does not fully support immutable entity types .

In ASP.NET Core it makes sense to use records for responses, but not as request class.

C#
 1public record class PersonResponse(string FirstName, string LastName);
 2
 3public class PersonRequest
 4{
 5    public string? FirstName { get; set; }
 6    public string? LastName { get; set; }
 7
 8    // see https://benfoster.io/blog/minimal-api-validation-endpoint-filters/
 9    public class Validator : AbstractValidator<PersonRequest>
10    {
11        public Validator()
12        {
13            RuleFor(x => x.FirstName).NotEmpty();
14            RuleFor(x => x.LastName).NotEmpty();
15        }
16    }
17}
18
19app.MapPost("/person", ([FromBody] RegisterCustomerRequest request, IValidator<RegisterCustomerRequest> validator) =>
20{
21    var validationResult = validator.Validate(request);
22
23    if (validationResult.IsValid is false)
24    {
25        return Results.ValidationProblem(validationResult.ToDictionary(),
26        statusCode: HttpStatusCodes.Status400BadRequest);
27    }
28
29    // do stuff here, like add to database
30
31    PersonResponse response = new(request.FirstName, request.LastName);)
32    return Results.Ok(response);
33});

Rule of thumb

  • To be on the safe side, use a simple class. A class is supported in virtually all scenarios.
  • If you have a data structure that should be immutable, use a record (except in scenarios that are not technically supported).
  • Only use Struct… a) if the implementation is “small”, b) if only primitive types are used and c) if your Struct is not boxed. “Small” used to be defined as 16 bytes , but when using record structs, this does not necessarily make a big difference anymore.

Benchmarks

As part of my Sustainable Code series, I also have benchmarks of classes and structs - and the differences are impressive.

Shell
 1BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.5131/22H2/2022Update)
 2AMD Ryzen 9 9950X, 1 CPU, 32 logical and 16 physical cores
 3.NET SDK 9.0.100
 4  [Host]   : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
 5  .NET 7.0 : .NET 7.0.20 (7.0.2024.26716), X64 RyuJIT AVX2
 6  .NET 8.0 : .NET 8.0.11 (8.0.1124.51707), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
 7  .NET 9.0 : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
 8
 9
10| Method       | Runtime  | Mean      | Error    | StdDev   | Ratio | RatioSD | Gen0   | Allocated |
11|------------- |--------- |----------:|---------:|---------:|------:|--------:|-------:|----------:|
12| SmallStruct  | .NET 7.0 |  26.13 ns | 0.073 ns | 0.069 ns |  1.04 |    0.01 |      - |         - |
13| SmallStruct  | .NET 8.0 |  26.17 ns | 0.054 ns | 0.048 ns |  1.05 |    0.01 |      - |         - |
14| SmallStruct  | .NET 9.0 |  25.03 ns | 0.239 ns | 0.212 ns |  1.00 |    0.01 |      - |         - |
15|              |          |           |          |          |       |         |        |           |
16| MediumStruct | .NET 7.0 |  26.32 ns | 0.129 ns | 0.120 ns |  1.03 |    0.01 |      - |         - |
17| MediumStruct | .NET 8.0 |  27.34 ns | 0.163 ns | 0.153 ns |  1.06 |    0.01 |      - |         - |
18| MediumStruct | .NET 9.0 |  25.68 ns | 0.315 ns | 0.279 ns |  1.00 |    0.01 |      - |         - |
19|              |          |           |          |          |       |         |        |           |
20| SmallClass   | .NET 7.0 | 129.75 ns | 1.158 ns | 1.083 ns |  1.00 |    0.02 | 0.1433 |    2400 B |
21| SmallClass   | .NET 8.0 | 128.01 ns | 0.599 ns | 0.531 ns |  0.98 |    0.02 | 0.1433 |    2400 B |
22| SmallClass   | .NET 9.0 | 130.45 ns | 2.612 ns | 3.008 ns |  1.00 |    0.03 | 0.1433 |    2400 B |
23|              |          |           |          |          |       |         |        |           |
24| MediumClass  | .NET 7.0 | 292.62 ns | 1.292 ns | 1.209 ns |  1.00 |    0.01 | 0.2389 |    4000 B |
25| MediumClass  | .NET 8.0 | 287.63 ns | 1.091 ns | 0.968 ns |  0.98 |    0.01 | 0.2389 |    4000 B |
26| MediumClass  | .NET 9.0 | 292.07 ns | 4.250 ns | 3.768 ns |  1.00 |    0.02 | 0.2389 |    4000 B |

See full code here 🌳 Sustainable Code - Records vs Structs 📊

You can see enormous performance advantages when structs are used correctly. However, if structs are used incorrectly - for example in the Boxing scenario , then all the advantages evaporate and the struct becomes a disadvantage: it becomes slower.

Shell
1| Method              | Runtime  | Mean      | Error     | StdDev    | Ratio | RatioSD | Gen0   | Allocated |
2| Class               | .NET 9.0 | 1.5723 ns | 0.0120 ns | 0.0100 ns |  1.00 |    0.01 | 0.0014 |      24 B |
3| ClassWithInterface  | .NET 9.0 | 1.5783 ns | 0.0123 ns | 0.0115 ns |  1.00 |    0.01 | 0.0014 |      24 B |
4| Struct              | .NET 9.0 | 0.0013 ns | 0.0018 ns | 0.0016 ns |     ? |       ? |      - |         - |
5| StructWithInterface | .NET 9.0 | 1.5944 ns | 0.0167 ns | 0.0140 ns |  1.00 |    0.01 | 0.0014 |      24 B |

Conclusion

Records not only make the code cleaner, they are also very powerful due to their immutability and value equality - if you use them correctly, especially with structs.

Used incorrectly, they become a disadvantage.

Related articles

Production Rate Limiting in ASP.NET Core

Nov 3, 2026 - 16 min read

Production Rate Limiting in ASP.NET Core

Most ASP.NET Core rate-limiting examples stop after registering a fixed window. That is enough to demonstrate the middleware, but it is not …

The Big Decimal Bugfix with .NET 11

Nov 2, 2026 - 11 min read

The Big Decimal Bugfix with .NET 11

For more than two decades, (decimal)1.23 returned 1.23m. It looked correct, it passed every test and it was a polite lie.

Distroless, Chiseled or Alpine: Choosing a .NET Base Image

Oct 27, 2026 - 10 min read

Distroless, Chiseled or Alpine: Choosing a .NET Base Image

Distroless, Chiseled and Alpine are often compared as three equivalent Linux distributions. They are not. Alpine is a distribution built …


BEN ABT

BEN ABT

Ben is Chief Engineering Officer and Principal Software Engineer at Medialesson.de, with a strong focus on .NET and Microsoft Azure. In his professional role, he designs and builds highly scalable platforms for hybrid and cloud-based scenarios. He also advises companies and C-levels on migrating to the sovereign cloud.

He runs myCSharp.de, the largest and most active German-speaking C# community forum, actively contributes to both his own and external open-source projects and regularly shares his knowledge through his blog, at community events, conferences and as a writer for technical magazines.

Let's Work Together

Looking for an experienced Platform Architect or Engineer for your next project? Whether it's cloud migration, platform modernization or building new solutions from scratch - I'm here to help you succeed.

New Platforms

Modernization

Training & Consulting

Contact Me
X Facebook LinkedIn WhatsApp
Back Next Blog Post

Open for work!

Contact Me
BEN ABT BEN ABT Software Engineer · Microsoft MVP Passionate about .NET, Azure, performance optimization and sustainable software engineering.

Medialesson GmbH Logo

Microsoft MVP Logo

Recent posts

View all
Production Rate Limiting in ASP.NET Core

Production Rate Limiting in ASP.NET Core

Nov 3, 2026
The Big Decimal Bugfix with .NET 11

The Big Decimal Bugfix with .NET 11

Nov 2, 2026
Distroless, Chiseled or Alpine: Choosing a .NET Base Image

Distroless, Chiseled or Alpine: Choosing a .NET Base Image

Oct 27, 2026
Stop Using nginx as Your Default Static File Server in Docker

Stop Using nginx as Your Default Static File Server in …

Oct 24, 2026
Containerizing .NET in 2026 Without a Dockerfile

Containerizing .NET in 2026 Without a Dockerfile

Oct 20, 2026

Tags

.NET (195) Csharp (99) ASP.NET Core (49) Azure (41) DevOps (17) Docker (15) Microsoft (14) EF Core (11) Microsoft MVP (11) Security (11) AI (10) GitHub (10) GitHub Copilot (10) ASP.NET (9) Synology (9) .NET 11 (8) Architecture (8) Raspberry (8) Azure Functions (7) Cloudflare (7)
benjamin-abt.com

About BEN

Benjamin Abt is a Software Engineer, Platform Architect and Microsoft MVP specializing in .NET, Azure, cloud architecture, performance optimization and sustainable software development.

Connect

  • Follow me on GitHub
  • Follow me on LinkedIn
  • Follow me on Mastodon
  • Microsoft MVP profile
  • Follow SchwabenCode on GitHub
  • Follow me on X

Quick Links

  • Portfolio
  • Blog
  • Workshops & Training
  • Publications
  • Get in Touch
  • Sitemap
Open for Work

© Copyright Benjamin Abt & SchwabenCode | 2011 - 2026

Content licensed under CC BY 4.0 unless otherwise noted.

  • Impressum
  • Privacy
  • Contact