
Learn C# records vs classes vs structs with real examples. Master when to use each type in 2026 with best practices, pitfalls, and performance tips. Start now!
One of the most common questions C# developers ask in 2026 is simple to state but tricky to answer: C# records vs classes vs structs — which one should you actually use? Since records arrived in C# 9 and matured through C# 10, 11, 12, and 13, the decision is no longer just "value type or reference type." You now have three first-class options for modeling data, and picking the wrong one leads to subtle bugs, poor performance, and code that's painful to maintain.
In this tutorial, we'll break down the difference between records, classes, and structs in C#, show practical runnable examples, and give you a clear decision framework so you always pick the right type. By the end, you'll understand not just how each works, but why you'd choose one over the others.
The Quick Answer: C# Records vs Classes vs Structs
Before we dive deep, here's the short version that most developers searching for "when to use record c#" are looking for:
- Use a class when you need a reference type with mutable state and identity (the default choice for most objects and services).
- Use a record when you're modeling immutable data and care about value-based equality (DTOs, API models, configuration, events).
- Use a struct when you have small, short-lived value types where avoiding heap allocations matters (coordinates, money, measurements).
That's the cheat sheet. But the real value is understanding the trade-offs behind value type vs reference type in C#, so let's look at each one properly.
Classes in C#: The Reference Type Default
A class is a reference type. When you create an instance, the object lives on the managed heap, and your variable holds a reference (a pointer) to it. Two variables can point to the same object, and equality is based on identity by default — not the values inside.
public class BankAccount
{
public string Owner { get; set; }
public decimal Balance { get; set; }
public void Deposit(decimal amount) => Balance += amount;
}
var a = new BankAccount { Owner = "Alex", Balance = 100m };
var b = a; // b references the SAME object
b.Deposit(50m);
Console.WriteLine(a.Balance); // 150 — both see the change
Console.WriteLine(a == b); // True — same reference (identity)
var c = new BankAccount { Owner = "Alex", Balance = 150m };
Console.WriteLine(a == c); // False — different objects, same data
Notice that a == c is False even though the data is identical. That's reference equality — the foundation of how classes behave. This is exactly what you want for objects that have identity and behavior: services, controllers, entities with a lifecycle, and anything with mutable state shared across your application.
When to use a class
- The object has identity that matters (a specific user, a specific order in your database).
- It carries significant behavior, not just data.
- State changes over time and is shared by reference.
- It's large, or you need inheritance hierarchies.
Classes remain the workhorse of C#. If you're unsure, a class is rarely a wrong answer — it's just sometimes not the best one.
C# Records: Immutable Data With Value Equality
Records are the headline feature in the C# records vs classes debate. A record is still a reference type by default (a record compiles to a class), but the compiler generates a lot of boilerplate for you and changes the equality semantics to value-based.
This means two records are equal if all their properties are equal — perfect for data that represents a value rather than an identity.
public record Person(string FirstName, string LastName, int Age);
var p1 = new Person("Jordan", "Lee", 30);
var p2 = new Person("Jordan", "Lee", 30);
Console.WriteLine(p1 == p2); // True — value equality!
Console.WriteLine(p1.Equals(p2)); // True
Console.WriteLine(p1); // Person { FirstName = Jordan, LastName = Lee, Age = 30 }
Compare that to the class example earlier: with a class, p1 == p2 would be False. The record gives you value equality, a readable ToString(), a deconstructor, and GetHashCode() — all for free.
Non-destructive mutation with with
Because records are designed for immutability, C# gives you the with expression to create a modified copy instead of mutating the original:
var original = new Person("Jordan", "Lee", 30);
var older = original with { Age = 31 }; // copy with one change
Console.WriteLine(original.Age); // 30 — unchanged
Console.WriteLine(older.Age); // 31
Console.WriteLine(original == older); // False — different values
This pattern is incredibly useful for state management, redux-style architectures, and any code where you want predictable, side-effect-free updates.
record class vs record struct
Since C# 10 you can also declare a record struct, which combines value-type storage with the record's auto-generated members. Use record struct for small immutable values where you also want the convenience records provide:
public readonly record struct Point(double X, double Y);
var origin = new Point(0, 0);
var moved = origin with { X = 5 }; // works on record structs too
Console.WriteLine(origin == new Point(0, 0)); // True — value equality
Console.WriteLine(moved); // Point { X = 5, Y = 0 }
The plain record keyword is shorthand for record class. Mark record struct types as readonly whenever possible to guarantee immutability and help the JIT optimize.
When to use a record
- DTOs and API request/response models — they're pure data and benefit from value equality.
- Domain events and messages — immutable by nature.
- Configuration objects read once and never mutated.
- Anywhere you'd previously write a class and then manually override
Equals,GetHashCode, andToString.
Structs in C#: Value Types and Performance
A struct is a value type. Unlike classes and records, a struct's data is stored inline — on the stack for local variables, or embedded directly inside its containing object. When you assign a struct, you copy the entire value. This is the core of the C# struct vs class distinction.
public struct Money
{
public decimal Amount { get; init; }
public string Currency { get; init; }
}
var wallet = new Money { Amount = 100m, Currency = "USD" };
var copy = wallet; // a full COPY is made
copy = copy with { Amount = 50m }; // 'with' works on structs in modern C#
Console.WriteLine(wallet.Amount); // 100 — original is untouched
Console.WriteLine(copy.Amount); // 50
Because structs are copied by value, there's no shared reference and (usually) no heap allocation. For small, frequently created values like points, vectors, dates, and money, this avoids garbage collection pressure and can dramatically improve performance in hot paths.
The struct trade-offs you must understand
Structs are not "faster classes." They have real costs if misused:
- Copy cost: Large structs are expensive to copy on every assignment and method call. Microsoft's guideline is to keep structs under ~16 bytes unless you pass them by
ref/in. - Boxing: Storing a struct in an
objector non-generic collection boxes it onto the heap, erasing the performance benefit. - Mutable structs are dangerous: Because of copy semantics, mutating a struct stored in a collection or property often changes a copy, not the original — a classic source of bugs. Prefer
readonly struct.
When to use a struct
- The type is small and represents a single value (coordinate, color, money, timestamp).
- It's immutable, or can be made
readonly. - You create many instances and want to avoid heap allocations.
- It logically behaves like a primitive — comparing by value makes sense.
Value Type vs Reference Type in C#: The Core Difference
Everything above comes down to one concept that every C# developer must master: value type vs reference type in C#.
- Reference types (class, record class): the variable holds a reference; assignment copies the reference; multiple variables can share one object; equality is identity-based by default.
- Value types (struct, record struct): the variable holds the data; assignment copies the whole value; each variable is independent; equality is value-based.
Records blur the line by giving reference types value-based equality while keeping reference storage. That's why records are so popular for data modeling — you get the convenient equality of a value type without the copy cost of a large struct.
Common Pitfalls and Best Practices in 2026
Here are the mistakes I see most often when teams adopt records and structs:
- Using a record for entities with identity. An EF Core entity with a database ID should usually be a class — value equality on mutable, tracked entities causes confusion.
- Making mutable structs. Always prefer
readonly structorreadonly record structto avoid copy-mutation bugs. - Large structs. If your struct has many fields, the copy cost can be worse than a class allocation. Benchmark before assuming structs are faster.
- Forgetting records can still be mutable. A
recordwithsetaccessors is mutable; useinitor positional parameters for true immutability. - Inheritance with records. Record equality accounts for the runtime type, which can surprise you in hierarchies — keep record inheritance shallow and intentional.
A solid default policy for 2026 codebases: records for data, classes for behavior and identity, structs for small performance-critical values.
C# Records vs Classes vs Structs: Decision Cheat Sheet
- Modeling pure data (DTO, event, config)? →
record - Object with identity, behavior, or shared mutable state? →
class - Small immutable value created in large numbers? →
readonly structorreadonly record struct - Need inheritance and polymorphism? →
class(or carefully,record class) - Need value equality without copy cost? →
record class
Conclusion: Choosing Between C# Records vs Classes and Structs
The C# records vs classes vs structs decision becomes easy once you focus on intent rather than syntax. Ask yourself two questions: Does this type have identity or is it just a value? and Is it immutable data or behavior-rich state? Those two answers point you straight to the right choice.
Key takeaways:
- Classes are reference types with identity-based equality — your default for services, entities, and mutable, behavior-rich objects.
- Records are reference types (by default) with value-based equality, immutability support, and
withexpressions — ideal for DTOs, events, and data modeling. - Structs are value types stored inline — best for small, immutable, high-frequency values where you want to avoid heap allocations.
- Always understand value type vs reference type in C#, prefer immutability, and benchmark before optimizing with structs.
Master these three and you'll write cleaner, faster, and more maintainable C# in 2026 and beyond. Pick the type that matches your data's intent, and the rest of your design tends to fall into place.
Your go-to resource for C#, .NET, and modern software development. Follow along for daily tutorials, tips, and real-world examples.
Comments
Post a Comment