Why Does ImmutableArray.Builder.ItemRef(Int32) Return ref readonly Instead of ref?
C# includes several advanced memory-handling features that help improve application performance while keeping data secure and predictable. One interesting example appears in ImmutableArray<T>.Builder, where the ItemRef(Int32) method returns ref readonly T instead of a writable ref T.
At first, this may look unnecessary because both reference types still point to the same internal memory location. However, the actual reason behind this implementation is to stop outside code from changing internal values directly while still allowing extremely fast access to data.
This small API decision plays an important role in preserving the behavior of immutable collections in .NET.
What Happens With a Normal ref Return?
A standard ref return exposes the original memory location directly to the caller. Since the returned reference is writable, the value can be modified instantly without creating another copy.
Example:
ref int number = ref values[0];
number = 25;
Here, updating number immediately changes the original value stored inside the array.
This technique is useful for performance-sensitive code because it avoids extra allocations and copying operations. However, unrestricted write access can become dangerous when working with collections that are expected to behave like immutable data structures.
How ref readonly Works
ref readonly also returns a direct reference to the original memory location, but the caller is limited to reading the value only.
Example:
ref readonly int number = ref values[0];
Console.WriteLine(number);
Because the value is not copied, performance remains efficient. At the same time, the runtime prevents accidental modifications through that reference.
This approach combines speed with additional protection.
Why Writable References Could Be Problematic
The builder used by ImmutableArray<T> internally stores elements inside a mutable array for efficiency. If the ItemRef() method exposed a writable reference, external code could directly overwrite values inside the builder’s private storage.
Example:
ref var item = ref builder.ItemRef(0);
item = updatedValue;
Direct modifications like this could bypass internal logic and weaken the guarantees provided by immutable collections.
Shared Memory and Collection Integrity
To improve performance, the builder may reuse its internal array when generating an immutable collection. This optimization reduces unnecessary copying, but it also means multiple objects may temporarily rely on the same underlying memory.
If writable references were allowed, a supposedly immutable collection could still be modified indirectly after creation. Returning ref readonly prevents this issue by blocking external write operations while still allowing fast access to stored values.
Benefits of Using ref readonly
Using ref readonly offers several advantages:
- Prevents unintended modification of internal storage
- Preserves immutable collection behavior
- Reduces unnecessary memory copying
- Improves performance when working with large structs
- Makes APIs safer and more predictable
The decision to return ref readonly from ImmutableArray<T>.Builder.ItemRef(Int32) is mainly about controlling access to internal memory while maintaining the reliability of immutable collections. Although array resizing can affect both writable and readonly references, resize handling is not the primary motivation behind this API design.
Instead, the goal is to provide efficient low-level access without exposing the internal data to unsafe modifications.
Faqs: