Mastering readonly struct in C#: Safer, Faster, and More Efficient Code
C# has steadily grown into a language that strikes a beautiful balance between developer productivity and high performance. One of the lesser-known but extremely powerful features added in recent years is the readonly struct
. This feature allows you to define immutable value types that are more efficient and safer than their mutable counterparts.
In this article, we will explore what readonly struct
is, why it was introduced, how it works, and most importantly, how you can use it to write faster and safer C# code. By the end, you’ll not only understand its syntax but also when and why to use it.
Setting the Stage: Value Types in C#
Before we dive into readonly struct
, let’s briefly review what a struct is in C#.
Struct Basics
Structs are value types: Unlike classes, which are reference types stored on the heap, structs live on the stack (in most cases) and are copied by value.
Common uses: Representing small, lightweight data structures like points, colors, ranges, and other data that logically forms a single value.
Performance benefits: Because they are value types, structs avoid heap allocation overhead and do not contribute to garbage collection pressure as much as classes.
The Problem with Mutable Structs
While structs can be convenient, they come with a subtle danger: mutability. Because structs are copied by value, if you pass a mutable struct to a method, any modifications happen to a copy — not the original — which can lead to confusing bugs.
Example:
A copy was created for the mutable struct
This behavior is correct, but it often surprises developers. What we need is a way to make structs immutable and safe by design.
Enter readonly struct
The readonly struct
modifier was introduced in C# 7.2 to solve this exact problem. By declaring a struct as readonly
, you tell the compiler that all its fields are immutable.
Syntax
The struct is now set to be readonly
In this example, Point
is now completely immutable. Once you construct a Point
, its X
and Y
values cannot be changed.
Benefits
Safety: The compiler enforces immutability, preventing accidental modification.
Performance: Eliminates hidden defensive copies that the compiler would normally generate to protect against unintended writes.
Clarity: Signals to other developers that this struct is intended to be a value object and should not be mutated.
Defensive Copies: The Hidden Performance Trap
One of the less obvious performance costs with mutable structs is something called a defensive copy.
If you have a mutable struct and you access it through something like a property or readonly field, the compiler must make a copy of it before allowing mutation — to prevent modifying the original instance in an unintended way.
Example:
The compiler gives a warning
The compiler issues a warning because you are trying to modify a property-returned struct. But even if you suppress the warning, you are actually modifying a copy, not the original. This is both inefficient and error-prone.
When you declare a struct as readonly
, the compiler no longer needs to make defensive copies because it knows the struct is immutable.
in
Parameters and readonly struct
C# 7.2 also introduced the in
modifier, which allows passing a value type by readonly reference. This pairs beautifully with readonly struct
.
Example:
Now passing the struct with the “in” keyword
Why This Matters
Passing by in
avoids copying large structs entirely while still guaranteeing they cannot be modified within the method — making your code both faster and safer.
Real-World Use Cases
Let’s look at scenarios where readonly struct
is particularly useful.
Use Case 1: Geometric Types
A vector example using readonly struct
Vectors are naturally immutable — it doesn’t make sense for them to change after creation. Marking them readonly
ensures they are safe and efficient.
Use Case 2: Ranges and Intervals
Another example with a range type structure
When working with collections or slicing, ranges should not mutate unexpectedly. Using readonly struct
makes them trustworthy.
Use Case 3: Interop and Performance-Critical Code
When writing high-performance libraries (e.g., parsers, math libraries, serialization), immutability helps the JIT (just-in-time compiler) produce more optimized machine code.
Under the Hood: How the Compiler Enforces Readonly
When you declare a struct as readonly
, the compiler:
Ensures all fields are themselves readonly or immutable.
Disallows mutating methods (except constructors).
Emits IL (Intermediate Language) metadata that enforces readonly semantics.
This means any attempt to mutate the struct will result in a compile-time error, giving you safety guarantees at no runtime cost.
Pitfalls and Best Practices
Pitfall 1: Large Structs
While readonly struct
is great, be cautious with very large structs. Passing large structs around, even by in
reference, can still have performance implications. As a rule of thumb, structs should be relatively small (e.g., under 16–24 bytes).
Pitfall 2: Mixing Mutable and Immutable Fields
If you forget to mark all fields as readonly, your struct may not be fully immutable, and you lose some of the compiler optimizations.
Pitfall 3: Boxing
Be mindful of scenarios where your struct gets boxed (converted to object
). Boxing copies the struct, which may be unintended.
Best Practice: Combine readonly struct
with in
Parameters
This combination maximizes performance and avoids accidental copies:
C# best practice
Performance Comparison
This table illustrates the difference between a mutable struct and a readonly struct:
C# mutable structs vs readonly structs
Benchmarks show measurable performance gains when using readonly struct
for frequently accessed small data types.
Summary
readonly struct
is one of those C# features that might seem small but delivers huge benefits when applied thoughtfully:
Immutability by design – safer code, fewer bugs.
Performance boost – eliminates hidden copies.
Expressiveness – makes your intent clear to both the compiler and other developers.
If you are building value types that are not supposed to mutate, always consider making them readonly struct
. Pair it with in
parameters when passing them to methods, and you’ll get both safety and speed.
In short: readonly struct
gives you the best of both worlds — the performance benefits of value types with the safety of immutability.
Start using it in your geometric types, ranges, time intervals, and any value object where mutation does not make sense. Your future self (and your users) will thank you for the safer, faster code.
Ready to try it out? Refactor one of your existing structs to a readonly struct
today and measure the difference — you might be surprised how much cleaner and faster your code becomes.
If you want to keep the discussion going, head over to our Facebook group or Skool Community.