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.
6 using System
.Diagnostics
;
7 using System
.Runtime
.CompilerServices
;
8 using System
.Runtime
.InteropServices
;
12 internal ref partial struct ValueStringBuilder
14 private char[] _arrayToReturnToPool
;
15 private Span
<char> _chars
;
18 public ValueStringBuilder(Span
<char> initialBuffer
)
20 _arrayToReturnToPool
= null;
21 _chars
= initialBuffer
;
25 public ValueStringBuilder(int initialCapacity
)
27 _arrayToReturnToPool
= ArrayPool
<char>.Shared
.Rent(initialCapacity
);
28 _chars
= _arrayToReturnToPool
;
37 Debug
.Assert(value >= 0);
38 Debug
.Assert(value <= _chars
.Length
);
43 public int Capacity
=> _chars
.Length
;
45 public void EnsureCapacity(int capacity
)
47 if (capacity
> _chars
.Length
)
48 Grow(capacity
- _chars
.Length
);
52 /// Get a pinnable reference to the builder.
53 /// Does not ensure there is a null char after <see cref="Length"/>
54 /// This overload is pattern matched in the C# 7.3+ compiler so you can omit
55 /// the explicit method call, and write eg "fixed (char* c = builder)"
57 public ref char GetPinnableReference()
59 return ref MemoryMarshal
.GetReference(_chars
);
63 /// Get a pinnable reference to the builder.
65 /// <param name="terminate">Ensures that the builder has a null char after <see cref="Length"/></param>
66 public ref char GetPinnableReference(bool terminate
)
70 EnsureCapacity(Length
+ 1);
71 _chars
[Length
] = '\0';
73 return ref MemoryMarshal
.GetReference(_chars
);
76 public ref char this[int index
]
80 Debug
.Assert(index
< _pos
);
81 return ref _chars
[index
];
85 public override string ToString()
87 var s
= _chars
.Slice(0, _pos
).ToString();
92 /// <summary>Returns the underlying storage of the builder.</summary>
93 public Span
<char> RawChars
=> _chars
;
96 /// Returns a span around the contents of the builder.
98 /// <param name="terminate">Ensures that the builder has a null char after <see cref="Length"/></param>
99 public ReadOnlySpan
<char> AsSpan(bool terminate
)
103 EnsureCapacity(Length
+ 1);
104 _chars
[Length
] = '\0';
106 return _chars
.Slice(0, _pos
);
109 public ReadOnlySpan
<char> AsSpan() => _chars
.Slice(0, _pos
);
110 public ReadOnlySpan
<char> AsSpan(int start
) => _chars
.Slice(start
, _pos
- start
);
111 public ReadOnlySpan
<char> AsSpan(int start
, int length
) => _chars
.Slice(start
, length
);
113 public bool TryCopyTo(Span
<char> destination
, out int charsWritten
)
115 if (_chars
.Slice(0, _pos
).TryCopyTo(destination
))
129 public void Insert(int index
, char value, int count
)
131 if (_pos
> _chars
.Length
- count
)
136 int remaining
= _pos
- index
;
137 _chars
.Slice(index
, remaining
).CopyTo(_chars
.Slice(index
+ count
));
138 _chars
.Slice(index
, count
).Fill(value);
142 public void Insert(int index
, string s
)
144 int count
= s
.Length
;
146 if (_pos
> (_chars
.Length
- count
))
151 int remaining
= _pos
- index
;
152 _chars
.Slice(index
, remaining
).CopyTo(_chars
.Slice(index
+ count
));
153 s
.AsSpan().CopyTo(_chars
.Slice(index
));
157 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
158 public void Append(char c
)
161 if ((uint)pos
< (uint)_chars
.Length
)
172 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
173 public void Append(string s
)
176 if (s
.Length
== 1 && (uint)pos
< (uint)_chars
.Length
) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc.
187 private void AppendSlow(string s
)
190 if (pos
> _chars
.Length
- s
.Length
)
195 s
.AsSpan().CopyTo(_chars
.Slice(pos
));
199 public void Append(char c
, int count
)
201 if (_pos
> _chars
.Length
- count
)
206 Span
<char> dst
= _chars
.Slice(_pos
, count
);
207 for (int i
= 0; i
< dst
.Length
; i
++)
214 public unsafe void Append(char* value, int length
)
217 if (pos
> _chars
.Length
- length
)
222 Span
<char> dst
= _chars
.Slice(_pos
, length
);
223 for (int i
= 0; i
< dst
.Length
; i
++)
230 public void Append(ReadOnlySpan
<char> value)
233 if (pos
> _chars
.Length
- value.Length
)
238 value.CopyTo(_chars
.Slice(_pos
));
239 _pos
+= value.Length
;
242 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
243 public Span
<char> AppendSpan(int length
)
246 if (origPos
> _chars
.Length
- length
)
251 _pos
= origPos
+ length
;
252 return _chars
.Slice(origPos
, length
);
255 [MethodImpl(MethodImplOptions
.NoInlining
)]
256 private void GrowAndAppend(char c
)
262 [MethodImpl(MethodImplOptions
.NoInlining
)]
263 private void Grow(int requiredAdditionalCapacity
)
265 Debug
.Assert(requiredAdditionalCapacity
> 0);
267 char[] poolArray
= ArrayPool
<char>.Shared
.Rent(Math
.Max(_pos
+ requiredAdditionalCapacity
, _chars
.Length
* 2));
269 _chars
.CopyTo(poolArray
);
271 char[] toReturn
= _arrayToReturnToPool
;
272 _chars
= _arrayToReturnToPool
= poolArray
;
273 if (toReturn
!= null)
275 ArrayPool
<char>.Shared
.Return(toReturn
);
279 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
280 public void Dispose()
282 char[] toReturn
= _arrayToReturnToPool
;
283 this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again
284 if (toReturn
!= null)
286 ArrayPool
<char>.Shared
.Return(toReturn
);