@@ -24,6 +24,18 @@ public PooledStringBuilder(int capacity = _defaultCapacity)
2424 _disposed = false ;
2525 }
2626
27+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
28+ private void EnsureInitialized ( )
29+ {
30+ // If never constructed but not disposed, lazily initialize.
31+ if ( _buffer is null )
32+ {
33+ if ( _disposed ) ThrowDisposed ( ) ;
34+ _buffer = ArrayPool < char > . Shared . Rent ( _defaultCapacity ) ;
35+ _pos = 0 ;
36+ }
37+ }
38+
2739 public int Length
2840 {
2941 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
@@ -36,6 +48,7 @@ public int Capacity
3648 get
3749 {
3850 ThrowIfDisposed ( ) ;
51+ EnsureInitialized ( ) ;
3952 return _buffer ! . Length ;
4053 }
4154 }
@@ -44,23 +57,25 @@ public int Capacity
4457 public void Clear ( )
4558 {
4659 ThrowIfDisposed ( ) ;
60+ EnsureInitialized ( ) ;
4761 _pos = 0 ;
4862 }
4963
5064 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
5165 public ReadOnlySpan < char > AsSpan ( )
5266 {
5367 ThrowIfDisposed ( ) ;
68+ EnsureInitialized ( ) ;
5469 return _buffer ! . AsSpan ( 0 , _pos ) ;
5570 }
5671
5772 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
5873 public void EnsureCapacity ( int required )
5974 {
6075 ThrowIfDisposed ( ) ;
76+ EnsureInitialized ( ) ;
6177 if ( ( uint ) required <= ( uint ) _buffer ! . Length ) return ;
6278
63- // Round up to next power of two to keep rent/copy count low.
6479 int newSize = RoundUpPow2 ( required ) ;
6580 char [ ] newBuf = ArrayPool < char > . Shared . Rent ( newSize ) ;
6681 _buffer . AsSpan ( 0 , _pos )
@@ -74,8 +89,7 @@ public void EnsureCapacity(int required)
7489 public Span < char > AppendSpan ( int length )
7590 {
7691 ThrowIfDisposed ( ) ;
77- if ( length <= 0 )
78- return Span < char > . Empty ;
92+ if ( length <= 0 ) return Span < char > . Empty ;
7993
8094 int newPos = _pos + length ;
8195 EnsureCapacity ( newPos ) ;
@@ -88,6 +102,7 @@ public Span<char> AppendSpan(int length)
88102 public void Append ( char c )
89103 {
90104 ThrowIfDisposed ( ) ;
105+ EnsureInitialized ( ) ;
91106 int i = _pos ;
92107 if ( ( uint ) i >= ( uint ) _buffer ! . Length )
93108 {
@@ -103,8 +118,7 @@ public void Append(char c)
103118 public void Append ( string ? value )
104119 {
105120 ThrowIfDisposed ( ) ;
106- if ( string . IsNullOrEmpty ( value ) )
107- return ;
121+ if ( string . IsNullOrEmpty ( value ) ) return ;
108122
109123 ReadOnlySpan < char > src = value . AsSpan ( ) ;
110124 Span < char > dest = AppendSpan ( src . Length ) ;
@@ -143,20 +157,17 @@ public void Append(char c1, char c2, char c3)
143157 public void Append < T > ( T value , ReadOnlySpan < char > format = default , IFormatProvider ? provider = null ) where T : ISpanFormattable
144158 {
145159 ThrowIfDisposed ( ) ;
146- // Worst-case length guess to reduce retries (32 covers most primitives; dates may need more).
147160 var hint = 32 ;
148161
149162 while ( true )
150163 {
151164 Span < char > span = AppendSpan ( hint ) ;
152165 if ( value . TryFormat ( span , out int written , format , provider ) )
153166 {
154- // Adjust back if our hint was larger than actually written
155167 _pos -= ( hint - written ) ;
156168 return ;
157169 }
158170
159- // Not enough; roll back and grow, then retry.
160171 _pos -= hint ;
161172 hint <<= 1 ;
162173 EnsureCapacity ( _pos + hint ) ;
@@ -171,14 +182,14 @@ public void Append<T>(T value, ReadOnlySpan<char> format = default, IFormatProvi
171182 public void AppendSeparatorIfNotEmpty ( char separator )
172183 {
173184 ThrowIfDisposed ( ) ;
174- if ( _pos != 0 )
175- Append ( separator ) ;
185+ if ( _pos != 0 ) Append ( separator ) ;
176186 }
177187
178188 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
179189 public override string ToString ( )
180190 {
181191 ThrowIfDisposed ( ) ;
192+ EnsureInitialized ( ) ;
182193 return new string ( _buffer ! , 0 , _pos ) ;
183194 }
184195
@@ -187,7 +198,8 @@ public override string ToString()
187198 public string ToStringAndDispose ( bool clear = false )
188199 {
189200 ThrowIfDisposed ( ) ;
190- var s = new string ( _buffer ! , 0 , _pos ) ;
201+ EnsureInitialized ( ) ;
202+ string s = new string ( _buffer ! , 0 , _pos ) ;
191203 Dispose ( clear ) ;
192204 return s ;
193205 }
@@ -210,29 +222,23 @@ public void Dispose(bool clear)
210222 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
211223 private static int RoundUpPow2 ( int v )
212224 {
213- // clamp to positive
214- if ( v <= 0 )
215- return _defaultCapacity ;
225+ if ( v <= 0 ) return _defaultCapacity ;
216226
217- // next power-of-two (cap at Array.MaxLength-ish range)
218- var x = ( uint ) ( v - 1 ) ;
227+ uint x = ( uint ) ( v - 1 ) ;
219228 x |= x >> 1 ;
220229 x |= x >> 2 ;
221230 x |= x >> 4 ;
222231 x |= x >> 8 ;
223232 x |= x >> 16 ;
224233 x ++ ;
225- // Avoid excessively huge rents; ArrayPool may throw if extreme.
226234 const int max = 0x3FFFFFE0 ; // approx Array.MaxLength for char[]
227235 return ( int ) Math . Min ( x , max ) ;
228236 }
229237
230238 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
231239 private void ThrowIfDisposed ( )
232240 {
233- // Catch both: disposed and default-constructed
234- if ( _disposed || _buffer is null )
235- ThrowDisposed ( ) ;
241+ if ( _disposed ) ThrowDisposed ( ) ;
236242 }
237243
238244 [ MethodImpl ( MethodImplOptions . NoInlining ) ]
0 commit comments