ImmutableArray is a fixed-size immutable collection. Any modification produces a new instance.
- Read-only shared data
- Thread-safe data without locks
- Functional-style programming
See: samples/immutablearray-configuration-snapshot.cs
dotnet run samples/immutablearray-configuration-snapshot.csBacked by an array. Mutations create new arrays (copy-on-write behavior).
- Direct index access O(1)
- Each mutation creates a new array
- Higher allocation cost than List
- No internal resizing
- Index Lookups: Instant direct hardware array offset mapping.
- Item Mutations: Linear array copy replication, unless processed via an optimized staging container.
Compare building collections:
- ImmutableArray.Add
- List.Add
cd benchmarks
dotnet run -c Release -- --filter *Memory.ImmutableArrayBenchmark*benchmarks/Memory/ImmutableArrayBenchmark.cs
| Method | N | Mean | Gen 0 | Allocated |
|---|---|---|---|---|
| ImmutableArrayAdd | 1000 | 121,709.8404 ns | 968.9941 | 2,028,000 B |
| ImmutableArrayWithBuilderAdd | 1000 | 2,773.7270 ns | 5.9166 | 12,376 B |
| ListAdd | 1000 | 2,570.3695 ns | 4.0207 | 8,424 B |
| ImmutableArrayRead | 1000 | 0.6611 ns | - | - |
| ListRead | 1000 | 0.6676 ns | - | - |
| ImmutableArrayAdd | 10000 | 9,492,667.6649 ns | 95000.0000 | 200,280,000 B |
| ImmutableArrayWithBuilderAdd | 10000 | 27,527.2640 ns | 81.0547 | 171,352 B |
| ListAdd | 10000 | 25,606.4429 ns | 62.4695 | 131,400 B |
| ImmutableArrayRead | 10000 | 0.6695 ns | - | - |
| ListRead | 10000 | 0.6721 ns | - | - |
- The Item-by-Item Loop Catastrophe: The
ImmutableArrayAddmethod illustrates a severe architectural performance pitfall. Appending 10,000 items sequentially via the.Add()extension forces the runtime to re-allocate and duplicate 10,000 separate array instances. This drags execution speed down to 9.49 milliseconds and generates a massive 200.2 Megabytes of temporary heap trash. - The Builder Extinction Optimization: The
ImmutableArrayWithBuilderAddtrack fixes this memory issue completely. By utilizing the dedicated internal staging object (ImmutableArray.CreateBuilder<T>), items are compiled inside a single, resizable array. Calling.ToImmutable()then locks down that specific array reference. This brings loop insertion speed back down to 27.5 microseconds—making it 340x faster than a direct loop while removing 99.9% of the heap pollution. - The Raw Hardware Indexing Victory: On pure read paths (
ImmutableArrayRead), the collection runs faster than a standardList<T>(~0.66 ns vs ~0.67 ns). BecauseImmutableArray<T>wraps a raw memory array block with zero object proxy lookups or tracking abstraction logic, it maps index requests to direct memory pointer addresses with absolute maximum hardware speed.
- Use builder (ImmutableArray.CreateBuilder) for bulk operations
- Preallocate your Builder capacity bounds if scale limits are known: Just like a standard list or array track, initializing your staging container with an explicit starting boundary size (
ImmutableArray.CreateBuilder<T>(capacity)) prevents internal resizing array allocations during construction.
- Using for frequently changing data
- Attempting to define a Null default value inside data fields: As a non-nullable value-type structure, initializing fields using reference assignment styles triggers compiler issues. Always rely on
ImmutableArray<T>.Emptyto define clean, default empty collection spaces.
- Data is mostly read-only
- Thread safety required without locks
- When broadcasting frozen system configuration maps, routing rules, or validation tables across dozens of background threads without thread locks.
- Frequent mutations -> use List
- Inside short-lived algorithms where datasets are modified instantly before being immediately discarded.
Use ImmutableArray for read-heavy, write-rare scenarios.