🏗️ ImmutableArrayBuilder​
Overview​
ImmutableArrayBuilder<T> is a high-performance builder for ImmutableArray<T>, allowing efficient buffered writes before finalizing an immutable collection.
This structure minimizes memory allocations and uses buffer pooling (ArrayPool<T>) to enhance performance.
✨ Key Features​
- Pooled buffer allocation – avoids unnecessary GC allocations.
- Efficient appends – supports adding single values and bulk operations.
ReadOnlySpan<T>support – allows direct access to written data without allocations.- No heap allocations – uses
ref structto enforce stack-only usage. - Implements
IDisposable– ensures pooled memory is released properly.
🔹 How It Works​
🚀 Renting a Builder​
Instead of directly allocating memory, you rent a builder:
using var builder = ImmutableArrayBuilder<int>.Rent();
builder.Add(10);
builder.AddRange(new[] { 20, 30, 40 });
// Convert to an ImmutableArray
ImmutableArray<int> result = builder.ToImmutable();
When the using block exits, the pooled buffer is automatically returned.
🔹 Adding Items​
You can add elements dynamically:
builder.Add(5);
builder.AddRange(new int[] { 6, 7, 8 });
🔹 Accessing Data​
builder.WrittenSpan→ Provides a readonly view of written items.builder.ToArray()→ Returns a new array.builder.ToImmutable()→ Converts to an ImmutableArray.
🔹 Performance Optimization​
- Uses aggressive inlining (
[MethodImpl(MethodImplOptions.AggressiveInlining)]) for performance. ArrayPool<T>.Sharedhandles buffer reuse to reduce heap allocations.- Automatic buffer resizing ensures space without frequent reallocations.
🛠️ Internals & Implementation Details​
📌 ImmutableArrayBuilder<T>​
The main structure wraps a pooled writer:
internal ref struct ImmutableArrayBuilder<T>
{
private Writer? writer;
public static ImmutableArrayBuilder<T> Rent() => new(new Writer());
public static ImmutableArrayBuilder<T> Rent(int initialCapacity) => new(new Writer(initialCapacity));
public bool IsDefault => writer is null;
public ReadOnlySpan<T> WrittenSpan => writer!.WrittenSpan;
public void Add(T item) => writer!.Add(item);
public void AddRange(ReadOnlySpan<T> items) => writer!.AddRange(items);
public ImmutableArray<T> ToImmutable()
{
var array = writer!.WrittenSpan.ToArray();
return Unsafe.As<T[], ImmutableArray<T>>(ref array);
}
public void Dispose()
{
var w = writer;
writer = null;
w?.Dispose();
}
}
📌 Writer (Internal Buffer Manager)​
Handles buffer allocation, resizing, and disposal:
private sealed class Writer : ICollection<T>, IDisposable
{
private T?[]? array;
private int index;
public Writer() => array = ArrayPool<T?>.Shared.Rent(8);
public void Add(T value)
{
EnsureCapacity(1);
array![index++] = value;
}
public void Dispose()
{
var arr = array;
array = null;
if (arr is not null)
{
ArrayPool<T?>.Shared.Return(arr, clearArray: typeof(T) != typeof(char));
}
}
}
📌 Dynamic Resizing​
To avoid unnecessary allocations, the buffer doubles in size when needed:
private void EnsureCapacity(int requestedSize)
{
if (requestedSize > array!.Length - index)
{
ResizeBuffer(requestedSize);
}
}
A new pooled array is allocated only when necessary.
🔥 Performance Benefits​
| Feature | Benefit 🚀 |
|---|---|
| Stack-only usage | No GC pressure, super-fast execution. |
| Span-based API | Efficient memory access, avoids extra copies. |
| Pooled memory | Reduces allocations, increases performance. |
| Auto-growing buffer | Handles dynamic size needs. |
⚠️ Important Notes​
ImmutableArrayBuilder<T>is a ref struct, meaning:- It cannot be stored on the heap (e.g., in fields, async methods, or lambda captures).
- Always use it inside a method or a
usingblock.
- Call
.Dispose()manually if not usingusingto release resources.
🏆 Summary​
🔹 ImmutableArrayBuilder<T> is an optimized way to build immutable arrays efficiently without frequent allocations.
🔹 Uses buffer pooling, aggressive inlining, and stack-only constraints to minimize memory usage.
🔹 Ensures high-performance immutable array construction without GC overhead.
🚀 Use it in performance-critical scenarios where immutable collections are frequently created!