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 // See the LICENSE file in the project root for more information.
7 using System
.Diagnostics
;
8 using System
.Runtime
.CompilerServices
;
9 using System
.Runtime
.InteropServices
;
13 internal ref partial struct ValueStringBuilder
15 private char[]? _arrayToReturnToPool
;
16 private Span
<char> _chars
;
19 public ValueStringBuilder(Span
<char> initialBuffer
)
21 _arrayToReturnToPool
= null;
22 _chars
= initialBuffer
;
26 public ValueStringBuilder(int initialCapacity
)
28 _arrayToReturnToPool
= ArrayPool
<char>.Shared
.Rent(initialCapacity
);
29 _chars
= _arrayToReturnToPool
;
38 Debug
.Assert(value >= 0);
39 Debug
.Assert(value <= _chars
.Length
);
44 public int Capacity
=> _chars
.Length
;
46 public void EnsureCapacity(int capacity
)
48 if (capacity
> _chars
.Length
)
49 Grow(capacity
- _pos
);
53 /// Get a pinnable reference to the builder.
54 /// Does not ensure there is a null char after <see cref="Length"/>
55 /// This overload is pattern matched in the C# 7.3+ compiler so you can omit
56 /// the explicit method call, and write eg "fixed (char* c = builder)"
58 public ref char GetPinnableReference()
60 return ref MemoryMarshal
.GetReference(_chars
);
64 /// Get a pinnable reference to the builder.
66 /// <param name="terminate">Ensures that the builder has a null char after <see cref="Length"/></param>
67 public ref char GetPinnableReference(bool terminate
)
71 EnsureCapacity(Length
+ 1);
72 _chars
[Length
] = '\0';
74 return ref MemoryMarshal
.GetReference(_chars
);
77 public ref char this[int index
]
81 Debug
.Assert(index
< _pos
);
82 return ref _chars
[index
];
86 public override string ToString()
88 string s
= _chars
.Slice(0, _pos
).ToString();
93 /// <summary>Returns the underlying storage of the builder.</summary>
94 public Span
<char> RawChars
=> _chars
;
97 /// Returns a span around the contents of the builder.
99 /// <param name="terminate">Ensures that the builder has a null char after <see cref="Length"/></param>
100 public ReadOnlySpan
<char> AsSpan(bool terminate
)
104 EnsureCapacity(Length
+ 1);
105 _chars
[Length
] = '\0';
107 return _chars
.Slice(0, _pos
);
110 public ReadOnlySpan
<char> AsSpan() => _chars
.Slice(0, _pos
);
111 public ReadOnlySpan
<char> AsSpan(int start
) => _chars
.Slice(start
, _pos
- start
);
112 public ReadOnlySpan
<char> AsSpan(int start
, int length
) => _chars
.Slice(start
, length
);
114 public bool TryCopyTo(Span
<char> destination
, out int charsWritten
)
116 if (_chars
.Slice(0, _pos
).TryCopyTo(destination
))
130 public void Insert(int index
, char value, int count
)
132 if (_pos
> _chars
.Length
- count
)
137 int remaining
= _pos
- index
;
138 _chars
.Slice(index
, remaining
).CopyTo(_chars
.Slice(index
+ count
));
139 _chars
.Slice(index
, count
).Fill(value);
143 public void Insert(int index
, string? s
)
150 int count
= s
.Length
;
152 if (_pos
> (_chars
.Length
- count
))
157 int remaining
= _pos
- index
;
158 _chars
.Slice(index
, remaining
).CopyTo(_chars
.Slice(index
+ count
));
159 s
.AsSpan().CopyTo(_chars
.Slice(index
));
163 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
164 public void Append(char c
)
167 if ((uint)pos
< (uint)_chars
.Length
)
178 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
179 public void Append(string? s
)
187 if (s
.Length
== 1 && (uint)pos
< (uint)_chars
.Length
) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc.
198 private void AppendSlow(string s
)
201 if (pos
> _chars
.Length
- s
.Length
)
206 s
.AsSpan().CopyTo(_chars
.Slice(pos
));
210 public void Append(char c
, int count
)
212 if (_pos
> _chars
.Length
- count
)
217 Span
<char> dst
= _chars
.Slice(_pos
, count
);
218 for (int i
= 0; i
< dst
.Length
; i
++)
225 public unsafe void Append(char* value, int length
)
228 if (pos
> _chars
.Length
- length
)
233 Span
<char> dst
= _chars
.Slice(_pos
, length
);
234 for (int i
= 0; i
< dst
.Length
; i
++)
241 public void Append(ReadOnlySpan
<char> value)
244 if (pos
> _chars
.Length
- value.Length
)
249 value.CopyTo(_chars
.Slice(_pos
));
250 _pos
+= value.Length
;
253 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
254 public Span
<char> AppendSpan(int length
)
257 if (origPos
> _chars
.Length
- length
)
262 _pos
= origPos
+ length
;
263 return _chars
.Slice(origPos
, length
);
266 [MethodImpl(MethodImplOptions
.NoInlining
)]
267 private void GrowAndAppend(char c
)
274 /// Resize the internal buffer either by doubling current buffer size or
275 /// by adding <paramref name="additionalCapacityBeyondPos"/> to
276 /// <see cref="_pos"/> whichever is greater.
278 /// <param name="additionalCapacityBeyondPos">
279 /// Number of chars requested beyond current position.
281 [MethodImpl(MethodImplOptions
.NoInlining
)]
282 private void Grow(int additionalCapacityBeyondPos
)
284 Debug
.Assert(additionalCapacityBeyondPos
> 0);
285 Debug
.Assert(_pos
> _chars
.Length
- additionalCapacityBeyondPos
, "Grow called incorrectly, no resize is needed.");
287 char[] poolArray
= ArrayPool
<char>.Shared
.Rent(Math
.Max(_pos
+ additionalCapacityBeyondPos
, _chars
.Length
* 2));
289 _chars
.Slice(0, _pos
).CopyTo(poolArray
);
291 char[]? toReturn
= _arrayToReturnToPool
;
292 _chars
= _arrayToReturnToPool
= poolArray
;
293 if (toReturn
!= null)
295 ArrayPool
<char>.Shared
.Return(toReturn
);
299 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
300 public void Dispose()
302 char[]? toReturn
= _arrayToReturnToPool
;
303 this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again
304 if (toReturn
!= null)
306 ArrayPool
<char>.Shared
.Return(toReturn
);