Pooled and stack-only counterparts to List<T>, Dictionary<TKey,TValue>, Stack<T> and Queue<T>
whose ref struct forms add, look up and enumerate with 0 B allocated, NativeAOT-ready.
- 0 B on the hot path, asserted in CI. The
ref structcollections add, look up and enumerate with 0 B allocated per call;AllocationTestsfails CI if any of them allocates. - Faster than the BCL as well. Adding 1,000 items to a
PooledList<int>takes 0.63× the time ofList<int>, which allocates 8,424 B in the same run. Stacks, queues and dictionaries take 0.6–0.8×, and aRingBuffer<int>takes 0.47× a pre-sizedQueue<int>. - Works under NativeAOT and trimming. No reflection and no runtime code generation; every PR publishes and runs an AOT sample.
- Compile-time diagnostics instead of runtime surprises.
ZAC001warns when a pooled collection is never disposed, so a buffer that would never return to the pool is a build warning.
dotnet add package ZeroAlloc.Collectionsusing ZeroAlloc.Collections;
string[] order = ["apple", "pear", "apple", "kiwi"];
// Rents its entries from ArrayPool<T>.Shared and returns them on Dispose.
using var prices = new SpanDictionary<string, decimal>(capacity: 16);
prices.Add("apple", 0.50m);
prices.Add("pear", 0.75m);
// Lives on the stack: no heap allocation at all.
var basket = new FixedSizeList<decimal>(stackalloc decimal[8]);
foreach (var item in order)
if (prices.TryGetValue(item, out var price)) basket.Add(price);
decimal total = 0;
foreach (var p in basket) total += p;
Console.WriteLine($"{basket.Count} items, {total}"); // 3 items, 1.75| Type | Ref Struct | Heap Variant | Description |
|---|---|---|---|
PooledList<T> |
Yes | HeapPooledList<T> |
Pooled-backed growable list |
RingBuffer<T> |
Yes | HeapRingBuffer<T> |
Fixed-capacity circular buffer |
SpanDictionary<TKey,TValue> |
Yes | HeapSpanDictionary<TKey,TValue> |
Open-addressing hash map |
PooledStack<T> |
Yes | HeapPooledStack<T> |
Pooled-backed LIFO stack |
PooledQueue<T> |
Yes | HeapPooledQueue<T> |
Pooled-backed FIFO queue |
FixedSizeList<T> |
Yes | HeapFixedSizeList<T> |
Stack-allocated fixed-capacity list |
ConcurrentHeapSpanDictionary<TKey,TValue> |
No | — | Thread-safe, coarse-locked, pooled hash map |
- Two forms of each: a
ref structfor synchronous hot paths, and aHeap*class for fields,asynccode and APIs that takeIList<T>,IReadOnlyCollection<T>orIDictionary<TKey,TValue>. Span<T>access to the list and stack contents for zero-copy loops.- Source generators that emit type-specific collections from
[ZeroAllocList(typeof(T))]. See source generators. - Targets
netstandard2.1,net8.0andnet9.0.
BenchmarkDotNet, N = 1,000. Time is the same-run ratio against the BCL type; allocations are exact.
| Workload | BCL type | ZeroAlloc time | BCL alloc | ZeroAlloc alloc |
|---|---|---|---|---|
| Add N items | List<int> |
0.63× | 8,424 B | 0 B |
| Push and pop N items | Stack<int> |
0.62× | 8,424 B | 0 B |
| Enqueue and dequeue N items | Queue<int> |
0.79× | 8,432 B | 0 B |
| Write and read N items, capacity known | Queue<int> |
0.47× | 4,064 B | 0 B |
The Heap* classes allocate one enumerator per foreach. Every workload, with the full numbers: docs/performance.md.
- Documentation: collections.zeroalloc.net, including getting started, the cookbook and diagnostics.
- Related packages: ZeroAlloc.Inject and ZeroAlloc.Analyzers.
- Contributing: issues and pull requests are welcome on GitHub.
- CHANGELOG.
MIT. See LICENSE.