InlineCollections provides high-performance, zero-allocation collection primitives for .NET with a fixed capacity of 32 elements. The collections are implemented as ref struct types optimized for ultra-low latency scenarios where heap allocations must be eliminated.
InlineList32<T>, InlineStack32<T>, and InlineQueue32<T> provide stack-allocated storage via the InlineArray language feature (C# 12+), enabling:
- โจ Zero heap allocations for the fast path
- ๐๏ธ Minimal GC pressure
- ๐ฏ Predictable memory layout for cache optimization
- ๐ก๏ธ High-throughput, low-latency execution
Important
Positioning Statement: This library is not a general-purpose replacement for the standard BCL collections types. Standard collections are designed for flexibility and large datasets. InlineCollections are "surgical tools" designed for High-Performance hot-paths where the developer has a guaranteed bound on the number of elements (โค 32) and must eliminate heap allocations to reduce GC pressure and latency.
Add the package to your project via .NET CLI:
dotnet add package InlineCollections --version 0.1.0InlineCollections are ref struct types designed for stack allocation. They ensure Zero Heap Allocation for up to 32 elements.
using InlineCollections;
// Initialize on stack (Zero Allocation)
var list = new InlineList32<int>();
list.Add(10);
list.Add(20);
// High-performance iteration (Modify in-place via Span)
foreach (ref int item in list.AsSpan())
{
item += 1;
}Standard .NET collections (List<T>, Stack<T>, Queue<T>) allocate on the heap, requiring GC overhead and cache misses for small working sets. In high-performance scenarios (real-time systems, game engines, network processors, serialization hotspots), this overhead is unacceptable. InlineCollections eliminates allocations by storing 32 elements inline within the struct itself.
Performance highlights
- โก Zero Allocations โ for up to 32 elements these collections avoid heap allocations.
- ๐๏ธ Reduced GC Pressure โ fewer short-lived allocations means fewer GC cycles and pauses.
- โ๏ธ Predictable Latency โ bounded-capacity operations reduce variance in execution time.
- โก Hot-path code that creates many short-lived small collections
- โฑ๏ธ Real-time systems requiring predictable latency
- ๐ฎ Game engine frame-local processing (per-frame temporary buffers)
- โก Network packet processing and protocol parsing
- ๐ Serialization/deserialization buffers where allocations matter
- ๐ง Stack-like or frame-local data with bounded depth
- Collections that routinely exceed 32 elements
- Scenarios requiring thread-safety or concurrent access
- Reference-type or nullable element types
- When API compatibility with
List<T>is required - Managed heap scenarios where GC pressure is not a primary concern
Each collection type uses the InlineArray32<T> struct, which leverages the [InlineArray(32)] attribute to embed 32 elements directly inside the struct. This is a value-type collection stored on the stack (when not captured in a reference type).
- Inline storage: 32 elements stored as struct fields
- No heap allocation
ref structsemantics (no boxing, no reference storage)
- Unmanaged element types only (constraint:
T : unmanaged, IEquatable<T>) - Fixed capacity of exactly 32 elements
- Throws
InvalidOperationExceptionwhen capacity is exceeded - Value-type semantics: copies on assignment/parameter passing
A list with a maximum capacity of 32 unmanaged elements.
Key methods:
Add(T item)โ add to end; throws if fullTryAdd(T item)โ add to end; returns false if fullAddRange(ReadOnlySpan<T> items)โ bulk addInsert(int index, T item)โ insert at indexRemove(T item)โ remove first occurrenceRemoveAt(int index)โ remove by indexT this[int index]โ indexer with ref return for in-place modificationSpan<T> AsSpan()โ get current elements as a spanContains(T item)โ linear searchClear()โ empty the list
Example:
var list = new InlineList32<int>();
list.Add(1);
list.Add(2);
list.Add(3);
int value = list[0]; // 1
var span = list.AsSpan();
foreach (var item in span) {
Console.WriteLine(item);
}
foreach (var item in list) {
Console.WriteLine(item);
}LIFO (Last-In-First-Out) collection with maximum capacity of 32 elements.
Key methods:
Push(T item)โ push to stack; throws if fullTryPush(T item)โ push; returns false if fullT Pop()โ pop and return; throws if emptybool TryPop(out T result)โ pop safelyref T Peek()โ return ref to top without removing; throws if emptySpan<T> AsSpan()โ get all elements (in insertion order)Clear()โ empty the stack
Example:
var stack = new InlineStack32<int>();
stack.Push(10);
stack.Push(20);
stack.Push(30);
int top = stack.Pop(); // 30
int next = stack.Pop(); // 20
ref int peek = ref stack.Peek();
peek = 100; // modify in-place
foreach (var item in stack) {
Console.WriteLine(item); // Iterates in reverse order (LIFO)
}FIFO (First-In-First-Out) collection with maximum capacity of 32 elements. Internally uses a circular buffer for O(1) enqueue/dequeue.
Key methods:
Enqueue(T item)โ add to back; throws if fullTryEnqueue(T item)โ add safely; returns false if fullT Dequeue()โ remove and return from front; throws if emptybool TryDequeue(out T result)โ dequeue safelyref T Peek()โ return ref to front without removing; throws if emptyClear()โ empty the queue
Example:
var queue = new InlineQueue32<int>();
queue.Enqueue(1);
queue.Enqueue(2);
queue.Enqueue(3);
int first = queue.Dequeue(); // 1
int second = queue.Dequeue(); // 2
ref int front = ref queue.Peek();
front = 999; // modify in-place
foreach (var item in queue) {
Console.WriteLine(item); // Iterates in FIFO order
}- Fixed capacity: Exactly 32 elements; exceeding capacity throws
InvalidOperationException. - Unmanaged types only:
Tmust satisfyT : unmanaged, IEquatable<T>. - Value semantics: Assignment and parameter passing copy the entire struct.
- Struct size: Each collection is 32 * sizeof(T) bytes plus overhead. Large
Ttypes increase stack usage. - ref struct: Cannot be stored in fields of reference types or classes; cannot be boxed.
- No null elements: Elements must be valid unmanaged values.
- Exceptions:
InvalidOperationExceptionโ capacity exceeded or collection is empty (on Pop/Peek/Dequeue without Try- variant)ArgumentOutOfRangeExceptionโ invalid index (RemoveAt)
All operations are O(1) constant time except:
Remove(T item)โ O(n) linear search and shiftRemoveAt(int index)โ O(n) shifts remaining elementsInsert(int index, T item)โ O(n) shifts elements to the right
Indexer access (this[int index]) and Peek/Pop operations have zero bounds checking and are aggressively inlined.
For detailed benchmarks and comparisons with List<T>, Stack<T>, and Queue<T>, see docs/benchmarks.md.
- Architecture โ internal design and memory layout
- Design Philosophy โ principles and goals
- Memory Model โ stack vs heap, value semantics, ref safety
- Collections โ per-collection API reference and examples
- Performance โ benchmark methodology and results
- Limitations โ hard constraints and exceptions
- When to Use โ recommended scenarios
- When Not to Use โ scenarios to avoid
- FAQ โ common questions
BenchmarkDotNet results comparing InlineCollections with standard BCL collections are available in the benchmarks/ directory. Run:
dotnet run --project benchmarks/InlineCollections.Benchmarks -c ReleaseKey findings:
- Add operations: InlineList32 is 3-5x faster than List for small collections (zero allocations)
- Indexer access: Near-identical performance to List (both use direct memory access)
- Memory: Zero heap allocations vs one allocation for List
See docs/benchmarks.md for full results.
- Optimized for hot paths in high-performance systems
- Zero-allocation primitive for small collections
- Reference-type performance with stack allocation semantics
- Deterministic and cache-friendly
- A replacement for
List<T>,Stack<T>, orQueue<T> - A thread-safe or concurrent collection
- A general-purpose data structure for arbitrary workloads
- A garbage-collected or reference-type collection
Inline (InlineCollections):
- โ No allocation
- โ Cache-friendly
- โ Fast small operations
- โ Fixed capacity
- โ Struct copying cost
- โ Stack size cost
Heap (List):
- โ Dynamic capacity
- โ Unbounded growth
- โ No copying (reference type)
- โ Allocation overhead
- โ GC pressure
- โ Cache misses
Bounds-checked (List):
- โ Safe by default
- โ Branch misprediction overhead
Unchecked (InlineCollections):
- โ 0 branches in hot loops
- โ Faster indexing
- โ Caller must ensure valid indices (in practice, usually guaranteed)