C# readonly Keyword Usage: Fields, Structs, Ref Returns
c# readonly keyword usage: Learn how to use the C# readonly keyword for fields, structs, and ref returns, including practical examples and common pitfalls.
The readonly keyword in C# is often introduced as a simple way to make a field immutable, but its behavior changes depending on where you apply it. The c# readonly keyword usage spans fields, structs, reference returns, and in parameters, and each use case has its own constraints and performance implications. Knowing which form to choose and why requires understanding how the compiler enforces each one.
What readonly Means for Fields
A readonly field can only be assigned during declaration or in the constructor of the same class. Any attempt to assign it elsewhere produces a compile-time error. This is useful for values that are configuration-like and should not change after an object is built.
public class ServiceOptions { public readonly int TimeoutSeconds; public readonly string Endpoint; public ServiceOptions(int timeoutSeconds, string endpoint) { TimeoutSeconds = timeoutSeconds; Endpoint = endpoint; } }
The field is not a constant; it is a runtime value that cannot be reassigned after construction. This distinction matters because the value can differ per instance, unlike a const field which is compile-time and shared across all instances.
readonly vs const: Choosing the Right Immutability
const values are inlined at compile time, so they must be literals or other constants. readonly fields are evaluated at runtime, which means they can be assigned from method calls, configuration, or computed values. Use const when the value is truly a constant for all builds, such as a version number or a mathematical constant. Use readonly when the value is fixed per instance but not known at compile time.
| Aspect | const | readonly |
|---|---|---|
| Assigned at | Compile time | Runtime |
| Scope | Static, shared | Per instance (unless static readonly) |
| Allowed values | Literals, constants | Any expression in constructor |
| Memory | Inlined, no field storage | Stored in object or static storage |
A common mistake is using const for values that might change in future releases. Since const is inlined into every reference, changing the value requires recompiling all dependent assemblies. readonly avoids that because the value is read from the field at runtime.
Readonly Structs and Their Members
Marking a struct as readonly changes how the compiler treats its members. A readonly struct guarantees that no member can modify the struct's state. This enables the compiler to avoid defensive copies when passing the struct by reference, which can reduce overhead in performance-sensitive code.
public readonly struct Point { public int X { get; } public int Y { get; } public Point(int x, int y) { X = x; Y = y; } }
In a readonly struct, all instance fields must be readonly, and property setters are not allowed. You can also mark individual instance members of a non-readonly struct as readonly. This is useful when only one method needs to guarantee it won't mutate state.
public struct Rectangle { public double Width; public double Height; public readonly double Area => Width * Height; }
The readonly modifier on the Area property tells the compiler that evaluating it has no side effects. This allows the compiler to skip defensive copies when the struct is passed by in or ref.
Ref Readonly Returns and In Parameters
The readonly keyword also appears in ref readonly returns and in parameters. A ref readonly return allows you to return a reference to a field without allowing the caller to modify it. This is common in high-performance code where copying a large struct would be expensive.
public ref readonly Point GetOrigin() { return ref _origin; }
The caller can read the returned value but cannot assign to it. To use it, you must declare the receiver as ref readonly or just read it directly. The in parameter is the counterpart for method arguments: it passes a reference but prohibits modification.
public static double Distance(in Point a, in Point b) { double dx = a.X - b.X; double dy = a.Y - b.Y; return Math.Sqrt(dx * dx + dy * dy); }
These features are part of the broader effort to reduce copying in C#. They are especially relevant when working with large structs in tight loops.
Performance and Runtime Behavior
The readonly modifier itself does not change runtime performance for fields; it is a compile-time constraint. The performance benefit appears when using readonly structs and readonly members because the compiler can avoid defensive copies. When a struct is passed by value, the entire struct is copied. If a method does not modify the struct, the compiler may still copy it unless it can prove safety. readonly members provide that proof.
For example, calling a non-readonly property on a struct passed by in forces the compiler to create a local copy because the property might modify the struct. Marking the property as readonly eliminates that copy. This can reduce memory traffic in hot paths, though the exact impact depends on the struct size and call frequency.
It is important to note that readonly does not make the object thread-safe. It only prevents reassignment or mutation through the specific reference. If the field is a reference type, readonly prevents changing the reference, but the object itself can still be modified.
Common Mistakes and Edge Cases
One frequent mistake is applying readonly to a field of a mutable reference type and assuming the object cannot change. The readonly keyword only prevents reassigning the field, not calling methods on the object.
public readonly List<int> Items; public void AddItem(int value) { Items.Add(value); // Allowed }
Another edge case is using readonly with arrays. The array reference is readonly, but elements can still be modified. If you need a truly immutable collection, consider ReadOnlyCollection or immutable collections.
Also, be careful with readonly in structs. A readonly struct cannot have a field of a mutable reference type if that field is exposed publicly, because the object could still be mutated through the reference. The compiler enforces that all fields are readonly, but it does not make the referenced objects immutable.
When to Use Readonly: Practical Guidelines
Use readonly for fields that are set once during construction and represent the identity or configuration of an object. This makes the intent clear and prevents accidental reassignment later. Use readonly structs when you have small value types that are frequently passed around and you want to avoid defensive copies. Mark individual members as readonly when they are pure and you use in parameters or ref readonly returns.
Avoid using readonly for large reference types where the object itself is mutable and you need to change its contents. The keyword gives a false sense of immutability in that case. Also, do not use const for values that may change across versions, because const is inlined.
The c# readonly keyword usage is straightforward once you separate the three main applications: fields, structs, and references. Each has a distinct purpose and tradeoff, and choosing the right one depends on whether you need runtime assignment, copy avoidance, or a compile-time constant.