DEV Community

Cristian Sifuentes
Cristian Sifuentes

Posted on

Struct vs Record vs Class — Choosing the Right Type in Modern C# (.NET 9/10 Edition)

Struct vs Record vs Class — Choosing the Right Type in Modern C## (.NET 9/10 Edition)

Struct vs Record vs Class — Choosing the Right Type in Modern C## (.NET 9/10 Edition)


TL;DR

In modern C#, developers have three primary ways to model data types:

  • class
  • struct
  • record

All three allow you to define custom types.

But they behave very differently in terms of:

• memory allocation

• copying semantics

• equality behavior

• mutability

• performance

Choosing the wrong one can lead to:

• unnecessary allocations

• performance regressions

• incorrect equality comparisons

• subtle bugs in distributed systems

In modern .NET architectures, understanding these differences is essential.

This article breaks down exactly when to use each type, and why.


The Core Difference: Value vs Reference Semantics

Before we examine each type individually, we must understand the most fundamental distinction in .NET.

Types fall into two categories:

Value Types
→ stored directly
→ copied by value
→ typically allocated on the stack

Reference Types
→ stored on the heap
→ copied by reference
→ multiple variables can point to the same instance
Enter fullscreen mode Exit fullscreen mode

In C#:

struct  → value type
class   → reference type
record  → reference type (by default)
Enter fullscreen mode Exit fullscreen mode

Understanding this difference explains nearly everything else.


Class — The Default Choice

A class is the most common type in C#.

public class User
{
    public string Name { get; set; }
    public int Age { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Classes are reference types.

This means the variable does not store the object itself — it stores a reference to the object.

Example:

var user1 = new User { Name = "Alice", Age = 30 };
var user2 = user1;

user2.Name = "Bob";

Console.WriteLine(user1.Name);
Enter fullscreen mode Exit fullscreen mode

Output:

Bob
Enter fullscreen mode Exit fullscreen mode

Why?

Because both variables point to the same object in memory.

Memory diagram:

Stack
 ├─ user1 ─┐
 └─ user2 ─┘

Heap
 └─ User instance { Name = "Bob", Age = 30 }
Enter fullscreen mode Exit fullscreen mode

This behavior makes classes ideal for:

• domain entities

• mutable state

• complex object graphs

• dependency injection models

• application services

Classes represent identity.

Two objects may contain identical values but still represent different instances.

Example:

var a = new User { Name = "Alice", Age = 30 };
var b = new User { Name = "Alice", Age = 30 };

Console.WriteLine(a == b);
Enter fullscreen mode Exit fullscreen mode

Result:

false
Enter fullscreen mode Exit fullscreen mode

Even though the values match, they are different objects.


Struct — Lightweight Value Types

A struct behaves very differently.

public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Structs are value types.

This means the value itself is stored directly in memory.

Example:

var p1 = new Point { X = 10, Y = 20 };
var p2 = p1;

p2.X = 50;

Console.WriteLine(p1.X);
Enter fullscreen mode Exit fullscreen mode

Output:

10
Enter fullscreen mode Exit fullscreen mode

Why?

Because p2 received a copy of the value, not a reference.

Memory diagram:

Stack
 ├─ p1 { X=10, Y=20 }
 └─ p2 { X=50, Y=20 }
Enter fullscreen mode Exit fullscreen mode

This copying behavior makes structs extremely fast for small data types.

Common examples in .NET:

int
double
DateTime
Guid
Vector2
Span<T>
Enter fullscreen mode Exit fullscreen mode

These types are structs because they represent simple values.


When Structs Become Dangerous

Structs are powerful, but they must be used carefully.

Bad example:

public struct LargeObject
{
    public long A;
    public long B;
    public long C;
    public long D;
    public long E;
}
Enter fullscreen mode Exit fullscreen mode

Now imagine passing this struct through many method calls.

Each call copies the entire object.

Method → copy
Method → copy
Method → copy
Enter fullscreen mode Exit fullscreen mode

This can become more expensive than heap allocation.

Guideline used by many .NET engineers:

If a struct is larger than 16 bytes, reconsider using a class.

Another limitation:

Structs cannot inherit from other structs or classes.

They also cannot participate in complex inheritance hierarchies.


Record — Designed for Data Models

Records were introduced to simplify data-oriented programming.

Example:

public record User(string Name, int Age);
Enter fullscreen mode Exit fullscreen mode

This single line creates:

• immutable properties

• value-based equality

• a constructor

• deconstruction support

• a ToString() implementation

Records are reference types by default, but behave like value types when comparing equality.

Example:

var a = new User("Alice", 30);
var b = new User("Alice", 30);

Console.WriteLine(a == b);
Enter fullscreen mode Exit fullscreen mode

Output:

true
Enter fullscreen mode Exit fullscreen mode

Even though they are separate instances, records compare by value.

This behavior is ideal for:

• DTOs

• API models

• event messages

• immutable data objects


Immutability — The Hidden Power of Records

Records encourage immutability.

Example:

public record Order(Guid Id, decimal Total);
Enter fullscreen mode Exit fullscreen mode

Updating the object produces a new instance.

var order = new Order(Guid.NewGuid(), 100);

var updated = order with { Total = 120 };
Enter fullscreen mode Exit fullscreen mode

The original object remains unchanged.

This pattern is extremely valuable in:

• distributed systems

• event sourcing

• functional programming models

• multi-threaded applications

Immutability eliminates entire classes of bugs.


Record Structs

Modern C## also supports record structs.

Example:

public readonly record struct Coordinate(int X, int Y);
Enter fullscreen mode Exit fullscreen mode

This combines:

• value-type performance

• record-style equality

• immutable semantics

This is useful for high-performance data models.

Example use cases:

• game engines

• financial calculations

• physics simulations


Performance Considerations

Understanding memory behavior is critical for performance-sensitive applications.

Comparison summary:

Class
→ heap allocation
→ reference semantics
→ mutable by default

Struct
→ stack allocation (usually)
→ value semantics
→ copied by value

Record
→ reference type
→ value-based equality
→ immutable by design
Enter fullscreen mode Exit fullscreen mode

Performance implications:

Class
+ flexible
+ supports inheritance
- extra allocations

Struct
+ zero heap allocations
+ fast for small values
- expensive when large

Record
+ concise syntax
+ safe immutability
+ value equality
Enter fullscreen mode Exit fullscreen mode

Real Architecture Examples

Domain Entity

Use a class.

public class Order
{
    public Guid Id { get; set; }
    public decimal Total { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Domain entities have identity and mutable state.


DTO / API Response

Use a record.

public record OrderDto(Guid Id, decimal Total);
Enter fullscreen mode Exit fullscreen mode

DTOs represent data snapshots.

Value equality makes sense here.


Mathematical Value

Use a struct.

public readonly struct Vector2
{
    public float X { get; }
    public float Y { get; }
}
Enter fullscreen mode Exit fullscreen mode

Small immutable values benefit from value semantics.


Decision Framework

A practical decision rule:

Is the object large?
→ Use class

Is the object small and immutable?
→ Use struct

Is the object a data model?
→ Use record
Enter fullscreen mode Exit fullscreen mode

Another way to think about it:

Class   → identity
Struct  → value
Record  → data
Enter fullscreen mode Exit fullscreen mode

Why This Matters in Modern .NET

As .NET systems become:

• distributed

• concurrent

• event-driven

• high-performance

The distinction between value semantics and reference semantics becomes critical.

Incorrect choices can cause:

• unnecessary allocations

• synchronization bugs

• memory pressure

• subtle equality errors

Expert .NET developers treat type selection as an architectural decision.


Final Thoughts

The question is not:

Which type is best?

The real question is:

Which semantics match your data model?

class gives you identity and flexibility.

struct gives you performance and value semantics.

record gives you immutable data models with value equality.

Understanding these differences is one of the quiet skills that separates intermediate developers from experienced .NET engineers.


Written by Cristian Sifuentes

Software Engineer · .NET Architecture · Distributed Systems

Top comments (0)