Back to Blog
C#

C# Readonly Struct: When and How to Use

c# readonly struct: Learn how C# readonly structs prevent defensive copies, enforce immutability, and when they improve performance.

C#structsimmutabilityvalue typesperformance
Diagram showing a readonly struct preventing defensive copies in C#

When you mark a struct as readonly in C#, you tell the compiler that instances of that type are immutable. That single declaration changes how the compiler handles method calls and property access, and it can eliminate hidden copies that otherwise occur when a struct is passed by value.

The primary reason to use a c# readonly struct is to prevent defensive copies. When a struct is not readonly, the compiler sometimes creates a copy to ensure that a method cannot modify the original instance. With a readonly struct, the compiler can safely pass a reference to the instance without copying, because it knows the instance cannot be changed.

What a Readonly Struct Declares

Declaring a readonly struct is straightforward:

public readonly struct Point { public Point(double x, double y) { X = x; Y = y; } public double X { get; } public double Y { get; } }

All instance fields must be readonly, and auto-properties must be get-only. The compiler enforces this at compile time. You cannot assign to a field or property after construction. This makes the immutability contract explicit and verifiable.

How Readonly Structs Prevent Defensive Copies

The .NET compiler generates defensive copies in several scenarios. For example, when you access a member of a struct through a readonly reference, such as a field declared as readonly or a parameter passed with the in modifier, the compiler cannot guarantee that the member access does not mutate the struct. To protect against that, it creates a temporary copy of the struct and calls the member on the copy.

With a readonly struct, this protection is unnecessary. Because the compiler knows the instance is immutable, it can access members directly without copying. This eliminates the overhead of copying large value types and reduces memory traffic in performance-sensitive paths.

Readonly Members and Fields

You can also apply readonly to individual members of a non-readonly struct. For example:

public struct Distance { public readonly double Meters; public readonly double ToKilometers() => Meters / 1000.0; }

A readonly member cannot modify instance state. This is useful when you want to expose immutable operations without making the entire struct immutable. However, a readonly struct goes further: it guarantees that no member can mutate the instance, so the compiler can apply optimizations across all members.

Ref Readonly Returns and In Parameters

Readonly structs work naturally with in parameters and ref readonly returns. These features are designed to avoid copies when passing or returning value types.

public readonly struct Vector3 { public float X { get; } public float Y { get; } public float Z { get; } public Vector3(float x, float y, float z) { X = x; Y = y; Z = z; } } public static float Dot(in Vector3 a, in Vector3 b) { return a.X * b.X + a.Y * b.Y + a.Z * b.Z; }

Here, in passes the struct by reference without copying. If Vector3 were a regular struct, the compiler would still create defensive copies when accessing a.X because it cannot prove that the property getter does not mutate the instance. With a readonly struct, no copy is needed.

When to Use a Readonly Struct

Use a readonly struct when the type represents a value that is conceptually immutable and is small enough to remain a value type. Common examples include coordinates, measurements, identifiers, and configuration values. The decision depends on two factors: whether the data should never change after creation, and whether the type is used in hot paths where avoiding copies matters.

If the struct is large, consider whether a class might be more appropriate, because copying a large struct is expensive even without defensive copies. Readonly structs do not change the cost of copying the struct itself; they only eliminate the extra copies the compiler inserts for safety.

Performance Considerations and Tradeoffs

Performance benefits come from eliminating defensive copies, not from making the struct immutable. In code that passes structs by value or uses in parameters, a readonly struct can reduce memory allocations and CPU work. The exact gain depends on the size of the struct and how often it is used. There is no universal benchmark; the improvement is most visible in tight loops or high-frequency method calls.

A tradeoff is that readonly structs cannot be lazily initialized or have mutable caches. If you need to cache a computed value inside the struct, you must use a separate static cache or a different design. Also, readonly structs are not supported in C# versions before 7.2. If you are working with an older compiler, you need to update the language version.

Limitations and Compatibility

Because all fields must be readonly, you cannot use field initializers that depend on instance state. You also cannot declare events inside a readonly struct, since events require the ability to modify invocation lists. These constraints are intentional; they keep the type truly immutable.

When consuming a readonly struct from code that expects a mutable struct, you may need to convert it to a mutable form. This is rare, but it matters when integrating with APIs that require writable value types. In such cases, a regular struct or a class may be a better fit.

c# readonly struct: Practical Usage and Code Examples | RYUSLOG DEV