1+ using System . Diagnostics ;
2+ using System . Runtime . CompilerServices ;
3+
4+ namespace StackArrayBuilder ;
5+
6+ /// <summary>
7+ /// Helper type for avoiding allocations while building arrays.
8+ /// </summary>
9+ /// <typeparam name="T">The element type.</typeparam>
10+ /// <remarks>
11+ /// Will grow heap allocated size, if you need it.
12+ /// Only use grow in rare cases, as it needs to grow the array, if over already allocated size.
13+ /// If you are certain of the max size needed, you can use e.g. <code>StackArrayBuilder8</code>
14+ /// </remarks>
15+ public ref struct StackArrayBuilder < T >
16+ {
17+ private InlineArray16 < T > _stackAllocatedBuffer = default ;
18+ public const int StackAllocatedCapacity = 16 ;
19+ private const int DefaultHeapCapacity = 4 ;
20+
21+ private T [ ] ? _heapArrayBuffer ; // Starts out null, initialized if capacity is over stack allocated size when constructing or on Add.
22+ private int _count ; // Number of items added.
23+
24+ /// <summary>
25+ /// Initializes the <see cref="StackArrayBuilder{T}"/> with a specified capacity.
26+ /// </summary>
27+ /// <param name="capacity">The capacity of the array to allocate.</param>
28+ public StackArrayBuilder ( int capacity ) : this ( )
29+ {
30+ Debug . Assert ( capacity >= 0 ) ;
31+ if ( capacity > StackAllocatedCapacity )
32+ {
33+ _heapArrayBuffer = new T [ capacity - StackAllocatedCapacity ] ;
34+ }
35+ }
36+
37+ /// <summary>
38+ /// Gets the number of items this instance can store without re-allocating.
39+ /// <c>StackAllocatedCapacity</c> if the backing heap array is not needed, all up to that is already stack allocated
40+ /// </summary>
41+ /// <remarks>Only for unit testing, checking that overallocation does not happen</remarks>
42+ public int Capacity => _heapArrayBuffer ? . Length + StackAllocatedCapacity ?? StackAllocatedCapacity ;
43+
44+ /// <summary>
45+ /// Adds an item, resizing heap allocated array if necessary.
46+ /// </summary>
47+ /// <param name="item">The item to add.</param>
48+ public void Add ( T item )
49+ {
50+ if ( _count == Capacity )
51+ {
52+ EnsureCapacity ( _count + 1 ) ;
53+ }
54+
55+ UncheckedAdd ( item ) ;
56+ }
57+
58+ /// <summary>
59+ /// Creates an array from the contents of this builder.
60+ /// </summary>
61+ public T [ ] ToArray ( )
62+ {
63+ if ( _count == 0 )
64+ {
65+ return [ ] ;
66+ }
67+
68+ T [ ] result = new T [ _count ] ;
69+ int index = 0 ;
70+ foreach ( T stackAllocatedValue in _stackAllocatedBuffer )
71+ {
72+ result [ index ++ ] = stackAllocatedValue ;
73+ if ( index >= _count )
74+ {
75+ return result ;
76+ }
77+ }
78+
79+ _heapArrayBuffer . AsSpan ( 0 , _count - StackAllocatedCapacity ) . CopyTo ( result . AsSpan ( start : StackAllocatedCapacity ) ) ;
80+
81+ return result ;
82+ }
83+
84+ /// <summary>
85+ /// Adds an item, without checking if there is room.
86+ /// </summary>
87+ /// <param name="item">The item to add.</param>
88+ /// <remarks>
89+ /// Use this method if you know there is enough space in the <see cref="StackArrayBuilder{T}"/>
90+ /// for another item, and you are writing performance-sensitive code.
91+ /// </remarks>
92+ public void UncheckedAdd ( T item )
93+ {
94+ Debug . Assert ( _count < Capacity ) ;
95+ if ( _count < StackAllocatedCapacity )
96+ {
97+ _stackAllocatedBuffer [ _count ++ ] = item ;
98+ }
99+ else
100+ {
101+ _heapArrayBuffer ! [ _count ++ - StackAllocatedCapacity ] = item ;
102+ }
103+ }
104+
105+ private void EnsureCapacity ( int minimum )
106+ {
107+ Debug . Assert ( minimum > Capacity ) ;
108+
109+ if ( minimum < StackAllocatedCapacity )
110+ {
111+ return ; // There is still room on the stack
112+ }
113+
114+ if ( _heapArrayBuffer == null )
115+ {
116+ // Initial capacity has not been not set or too low, we will allocate the default heap array size
117+ _heapArrayBuffer = new T [ DefaultHeapCapacity ] ;
118+ return ;
119+ }
120+
121+ // Check if allocated heap capacity was enough
122+ int defaultCapacityWithHeap = _heapArrayBuffer . Length + StackAllocatedCapacity ;
123+ if ( defaultCapacityWithHeap >= minimum )
124+ {
125+ return ; // current allocated stack+heap is large enough
126+ }
127+
128+ // We need to allocate more heap capacity, by increasing the size of the array
129+ int nextHeapCapacity = 2 * _heapArrayBuffer . Length ;
130+
131+ if ( ( uint ) nextHeapCapacity > ( uint ) Array . MaxLength )
132+ {
133+ nextHeapCapacity = Math . Max ( _heapArrayBuffer . Length + 1 , Array . MaxLength ) ;
134+ }
135+
136+ nextHeapCapacity = Math . Max ( nextHeapCapacity , minimum ) ;
137+
138+ Array . Resize ( ref _heapArrayBuffer , nextHeapCapacity ) ;
139+ }
140+ }
0 commit comments