1
+ // Licensed to the .NET Foundation under one or more agreements.
2
+ // The .NET Foundation licenses this file to you under the MIT license.
3
+
4
+ // Copy/pasted from https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/System/Text/ValueStringBuilder.cs
5
+
6
+ using System ;
7
+ using System . Buffers ;
8
+ using System . Diagnostics ;
9
+ using System . Runtime . CompilerServices ;
10
+ using System . Runtime . InteropServices ;
11
+
12
+ #nullable enable
13
+
14
+ namespace Xtensive . Core
15
+ {
16
+ internal ref struct ValueStringBuilder
17
+ {
18
+ private char [ ] ? _arrayToReturnToPool ;
19
+ private Span < char > _chars ;
20
+ private int _pos ;
21
+
22
+ public ValueStringBuilder ( Span < char > initialBuffer )
23
+ {
24
+ _arrayToReturnToPool = null ;
25
+ _chars = initialBuffer ;
26
+ _pos = 0 ;
27
+ }
28
+
29
+ public ValueStringBuilder ( int initialCapacity )
30
+ {
31
+ _arrayToReturnToPool = ArrayPool < char > . Shared . Rent ( initialCapacity ) ;
32
+ _chars = _arrayToReturnToPool ;
33
+ _pos = 0 ;
34
+ }
35
+
36
+ public int Length
37
+ {
38
+ get => _pos ;
39
+ set {
40
+ Debug . Assert ( value >= 0 ) ;
41
+ Debug . Assert ( value <= _chars . Length ) ;
42
+ _pos = value ;
43
+ }
44
+ }
45
+
46
+ public int Capacity => _chars . Length ;
47
+
48
+ public void EnsureCapacity ( int capacity )
49
+ {
50
+ // This is not expected to be called this with negative capacity
51
+ Debug . Assert ( capacity >= 0 ) ;
52
+
53
+ // If the caller has a bug and calls this with negative capacity, make sure to call Grow to throw an exception.
54
+ if ( ( uint ) capacity > ( uint ) _chars . Length )
55
+ Grow ( capacity - _pos ) ;
56
+ }
57
+
58
+ /// <summary>
59
+ /// Get a pinnable reference to the builder.
60
+ /// Does not ensure there is a null char after <see cref="Length"/>
61
+ /// This overload is pattern matched in the C# 7.3+ compiler so you can omit
62
+ /// the explicit method call, and write eg "fixed (char* c = builder)"
63
+ /// </summary>
64
+ public ref char GetPinnableReference ( )
65
+ {
66
+ return ref MemoryMarshal . GetReference ( _chars ) ;
67
+ }
68
+
69
+ /// <summary>
70
+ /// Get a pinnable reference to the builder.
71
+ /// </summary>
72
+ /// <param name="terminate">Ensures that the builder has a null char after <see cref="Length"/></param>
73
+ public ref char GetPinnableReference ( bool terminate )
74
+ {
75
+ if ( terminate ) {
76
+ EnsureCapacity ( Length + 1 ) ;
77
+ _chars [ Length ] = '\0 ' ;
78
+ }
79
+
80
+ return ref MemoryMarshal . GetReference ( _chars ) ;
81
+ }
82
+
83
+ public ref char this [ int index ]
84
+ {
85
+ get {
86
+ Debug . Assert ( index < _pos ) ;
87
+ return ref _chars [ index ] ;
88
+ }
89
+ }
90
+
91
+ public override string ToString ( )
92
+ {
93
+ string s = _chars . Slice ( 0 , _pos ) . ToString ( ) ;
94
+ Dispose ( ) ;
95
+ return s ;
96
+ }
97
+
98
+ /// <summary>Returns the underlying storage of the builder.</summary>
99
+ public Span < char > RawChars => _chars ;
100
+
101
+ /// <summary>
102
+ /// Returns a span around the contents of the builder.
103
+ /// </summary>
104
+ /// <param name="terminate">Ensures that the builder has a null char after <see cref="Length"/></param>
105
+ public ReadOnlySpan < char > AsSpan ( bool terminate )
106
+ {
107
+ if ( terminate ) {
108
+ EnsureCapacity ( Length + 1 ) ;
109
+ _chars [ Length ] = '\0 ' ;
110
+ }
111
+
112
+ return _chars . Slice ( 0 , _pos ) ;
113
+ }
114
+
115
+ public ReadOnlySpan < char > AsSpan ( ) => _chars . Slice ( 0 , _pos ) ;
116
+ public ReadOnlySpan < char > AsSpan ( int start ) => _chars . Slice ( start , _pos - start ) ;
117
+ public ReadOnlySpan < char > AsSpan ( int start , int length ) => _chars . Slice ( start , length ) ;
118
+
119
+ public bool TryCopyTo ( Span < char > destination , out int charsWritten )
120
+ {
121
+ if ( _chars . Slice ( 0 , _pos ) . TryCopyTo ( destination ) ) {
122
+ charsWritten = _pos ;
123
+ Dispose ( ) ;
124
+ return true ;
125
+ }
126
+ else {
127
+ charsWritten = 0 ;
128
+ Dispose ( ) ;
129
+ return false ;
130
+ }
131
+ }
132
+
133
+ public void Insert ( int index , char value , int count )
134
+ {
135
+ if ( _pos > _chars . Length - count ) {
136
+ Grow ( count ) ;
137
+ }
138
+
139
+ int remaining = _pos - index ;
140
+ _chars . Slice ( index , remaining ) . CopyTo ( _chars . Slice ( index + count ) ) ;
141
+ _chars . Slice ( index , count ) . Fill ( value ) ;
142
+ _pos += count ;
143
+ }
144
+
145
+ public void Insert ( int index , string ? s )
146
+ {
147
+ if ( s == null ) {
148
+ return ;
149
+ }
150
+
151
+ int count = s . Length ;
152
+
153
+ if ( _pos > ( _chars . Length - count ) ) {
154
+ Grow ( count ) ;
155
+ }
156
+
157
+ int remaining = _pos - index ;
158
+ _chars . Slice ( index , remaining ) . CopyTo ( _chars . Slice ( index + count ) ) ;
159
+ #if NET6_0_OR_GREATE
160
+ s . CopyTo ( _chars . Slice ( index ) ) ;
161
+ #else
162
+ s . AsSpan ( ) . CopyTo ( _chars . Slice ( index ) ) ;
163
+ #endif
164
+ _pos += count ;
165
+ }
166
+
167
+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
168
+ public void Append ( char c )
169
+ {
170
+ int pos = _pos ;
171
+ Span < char > chars = _chars ;
172
+ if ( ( uint ) pos < ( uint ) chars . Length ) {
173
+ chars [ pos ] = c ;
174
+ _pos = pos + 1 ;
175
+ }
176
+ else {
177
+ GrowAndAppend ( c ) ;
178
+ }
179
+ }
180
+
181
+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
182
+ public void Append ( string ? s )
183
+ {
184
+ if ( s == null ) {
185
+ return ;
186
+ }
187
+
188
+ int pos = _pos ;
189
+ if ( s . Length == 1 &&
190
+ ( uint ) pos < ( uint ) _chars
191
+ . Length ) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc.
192
+ {
193
+ _chars [ pos ] = s [ 0 ] ;
194
+ _pos = pos + 1 ;
195
+ }
196
+ else {
197
+ AppendSlow ( s ) ;
198
+ }
199
+ }
200
+
201
+ private void AppendSlow ( string s )
202
+ {
203
+ int pos = _pos ;
204
+ if ( pos > _chars . Length - s . Length ) {
205
+ Grow ( s . Length ) ;
206
+ }
207
+
208
+ #if NET6_0_OR_GREATER
209
+ s . CopyTo ( _chars . Slice ( pos ) ) ;
210
+ #else
211
+ s . AsSpan ( ) . CopyTo ( _chars . Slice ( pos ) ) ;
212
+ #endif
213
+ _pos += s . Length ;
214
+ }
215
+
216
+ public void Append ( char c , int count )
217
+ {
218
+ if ( _pos > _chars . Length - count ) {
219
+ Grow ( count ) ;
220
+ }
221
+
222
+ Span < char > dst = _chars . Slice ( _pos , count ) ;
223
+ for ( int i = 0 ; i < dst . Length ; i ++ ) {
224
+ dst [ i ] = c ;
225
+ }
226
+
227
+ _pos += count ;
228
+ }
229
+
230
+ public void Append ( ReadOnlySpan < char > value )
231
+ {
232
+ int pos = _pos ;
233
+ if ( pos > _chars . Length - value . Length ) {
234
+ Grow ( value . Length ) ;
235
+ }
236
+
237
+ value . CopyTo ( _chars . Slice ( _pos ) ) ;
238
+ _pos += value . Length ;
239
+ }
240
+
241
+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
242
+ public Span < char > AppendSpan ( int length )
243
+ {
244
+ int origPos = _pos ;
245
+ if ( origPos > _chars . Length - length ) {
246
+ Grow ( length ) ;
247
+ }
248
+
249
+ _pos = origPos + length ;
250
+ return _chars . Slice ( origPos , length ) ;
251
+ }
252
+
253
+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
254
+ private void GrowAndAppend ( char c )
255
+ {
256
+ Grow ( 1 ) ;
257
+ Append ( c ) ;
258
+ }
259
+
260
+ /// <summary>
261
+ /// Resize the internal buffer either by doubling current buffer size or
262
+ /// by adding <paramref name="additionalCapacityBeyondPos"/> to
263
+ /// <see cref="_pos"/> whichever is greater.
264
+ /// </summary>
265
+ /// <param name="additionalCapacityBeyondPos">
266
+ /// Number of chars requested beyond current position.
267
+ /// </param>
268
+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
269
+ private void Grow ( int additionalCapacityBeyondPos )
270
+ {
271
+ Debug . Assert ( additionalCapacityBeyondPos > 0 ) ;
272
+ Debug . Assert ( _pos > _chars . Length - additionalCapacityBeyondPos ,
273
+ "Grow called incorrectly, no resize is needed." ) ;
274
+
275
+ const uint ArrayMaxLength = 0x7FFFFFC7 ; // same as Array.MaxLength
276
+
277
+ // Increase to at least the required size (_pos + additionalCapacityBeyondPos), but try
278
+ // to double the size if possible, bounding the doubling to not go beyond the max array length.
279
+ int newCapacity = ( int ) Math . Max (
280
+ ( uint ) ( _pos + additionalCapacityBeyondPos ) ,
281
+ Math . Min ( ( uint ) _chars . Length * 2 , ArrayMaxLength ) ) ;
282
+
283
+ // Make sure to let Rent throw an exception if the caller has a bug and the desired capacity is negative.
284
+ // This could also go negative if the actual required length wraps around.
285
+ char [ ] poolArray = ArrayPool < char > . Shared . Rent ( newCapacity ) ;
286
+
287
+ _chars . Slice ( 0 , _pos ) . CopyTo ( poolArray ) ;
288
+
289
+ char [ ] ? toReturn = _arrayToReturnToPool ;
290
+ _chars = _arrayToReturnToPool = poolArray ;
291
+ if ( toReturn != null ) {
292
+ ArrayPool < char > . Shared . Return ( toReturn ) ;
293
+ }
294
+ }
295
+
296
+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
297
+ public void Dispose ( )
298
+ {
299
+ char [ ] ? toReturn = _arrayToReturnToPool ;
300
+ this = default ; // for safety, to avoid using pooled array if this instance is erroneously appended to again
301
+ if ( toReturn != null ) {
302
+ ArrayPool < char > . Shared . Return ( toReturn ) ;
303
+ }
304
+ }
305
+ }
306
+ }
0 commit comments