diff --git a/.editorconfig b/.editorconfig index 3ce6343b..99b4fbaf 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,6 +3,7 @@ root = true # C# files [*.cs] +csharp_style_namespace_declarations=file_scoped:warning # Compiler dotnet_diagnostic.CS1998.severity = suggestion # CS1998: Missing awaits diff --git a/src/Common/CancellableStreamReader/ValueStringBuilder.cs b/src/Common/CancellableStreamReader/ValueStringBuilder.cs index 27bea693..9f1dedcd 100644 --- a/src/Common/CancellableStreamReader/ValueStringBuilder.cs +++ b/src/Common/CancellableStreamReader/ValueStringBuilder.cs @@ -8,310 +8,309 @@ #nullable enable -namespace System.Text +namespace System.Text; + +internal ref partial struct ValueStringBuilder { - internal ref partial struct ValueStringBuilder + private char[]? _arrayToReturnToPool; + private Span _chars; + private int _pos; + + public ValueStringBuilder(Span initialBuffer) { - private char[]? _arrayToReturnToPool; - private Span _chars; - private int _pos; + _arrayToReturnToPool = null; + _chars = initialBuffer; + _pos = 0; + } - public ValueStringBuilder(Span initialBuffer) + public ValueStringBuilder(int initialCapacity) + { + _arrayToReturnToPool = ArrayPool.Shared.Rent(initialCapacity); + _chars = _arrayToReturnToPool; + _pos = 0; + } + + public int Length + { + get => _pos; + set { - _arrayToReturnToPool = null; - _chars = initialBuffer; - _pos = 0; + Debug.Assert(value >= 0); + Debug.Assert(value <= _chars.Length); + _pos = value; } + } + + public int Capacity => _chars.Length; - public ValueStringBuilder(int initialCapacity) + public void EnsureCapacity(int capacity) + { + // This is not expected to be called this with negative capacity + Debug.Assert(capacity >= 0); + + // If the caller has a bug and calls this with negative capacity, make sure to call Grow to throw an exception. + if ((uint)capacity > (uint)_chars.Length) + Grow(capacity - _pos); + } + + /// + /// Get a pinnable reference to the builder. + /// Does not ensure there is a null char after + /// This overload is pattern matched in the C# 7.3+ compiler so you can omit + /// the explicit method call, and write eg "fixed (char* c = builder)" + /// + public ref char GetPinnableReference() + { + return ref MemoryMarshal.GetReference(_chars); + } + + /// + /// Get a pinnable reference to the builder. + /// + /// Ensures that the builder has a null char after + public ref char GetPinnableReference(bool terminate) + { + if (terminate) { - _arrayToReturnToPool = ArrayPool.Shared.Rent(initialCapacity); - _chars = _arrayToReturnToPool; - _pos = 0; + EnsureCapacity(Length + 1); + _chars[Length] = '\0'; } + return ref MemoryMarshal.GetReference(_chars); + } - public int Length + public ref char this[int index] + { + get { - get => _pos; - set - { - Debug.Assert(value >= 0); - Debug.Assert(value <= _chars.Length); - _pos = value; - } + Debug.Assert(index < _pos); + return ref _chars[index]; } + } - public int Capacity => _chars.Length; + public override string ToString() + { + string s = _chars.Slice(0, _pos).ToString(); + Dispose(); + return s; + } - public void EnsureCapacity(int capacity) - { - // This is not expected to be called this with negative capacity - Debug.Assert(capacity >= 0); + /// Returns the underlying storage of the builder. + public Span RawChars => _chars; - // If the caller has a bug and calls this with negative capacity, make sure to call Grow to throw an exception. - if ((uint)capacity > (uint)_chars.Length) - Grow(capacity - _pos); + /// + /// Returns a span around the contents of the builder. + /// + /// Ensures that the builder has a null char after + public ReadOnlySpan AsSpan(bool terminate) + { + if (terminate) + { + EnsureCapacity(Length + 1); + _chars[Length] = '\0'; } + return _chars.Slice(0, _pos); + } + + public ReadOnlySpan AsSpan() => _chars.Slice(0, _pos); + public ReadOnlySpan AsSpan(int start) => _chars.Slice(start, _pos - start); + public ReadOnlySpan AsSpan(int start, int length) => _chars.Slice(start, length); - /// - /// Get a pinnable reference to the builder. - /// Does not ensure there is a null char after - /// This overload is pattern matched in the C# 7.3+ compiler so you can omit - /// the explicit method call, and write eg "fixed (char* c = builder)" - /// - public ref char GetPinnableReference() + public bool TryCopyTo(Span destination, out int charsWritten) + { + if (_chars.Slice(0, _pos).TryCopyTo(destination)) { - return ref MemoryMarshal.GetReference(_chars); + charsWritten = _pos; + Dispose(); + return true; } - - /// - /// Get a pinnable reference to the builder. - /// - /// Ensures that the builder has a null char after - public ref char GetPinnableReference(bool terminate) + else { - if (terminate) - { - EnsureCapacity(Length + 1); - _chars[Length] = '\0'; - } - return ref MemoryMarshal.GetReference(_chars); + charsWritten = 0; + Dispose(); + return false; } + } - public ref char this[int index] + public void Insert(int index, char value, int count) + { + if (_pos > _chars.Length - count) { - get - { - Debug.Assert(index < _pos); - return ref _chars[index]; - } + Grow(count); } - public override string ToString() + int remaining = _pos - index; + _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count)); + _chars.Slice(index, count).Fill(value); + _pos += count; + } + + public void Insert(int index, string? s) + { + if (s == null) { - string s = _chars.Slice(0, _pos).ToString(); - Dispose(); - return s; + return; } - /// Returns the underlying storage of the builder. - public Span RawChars => _chars; + int count = s.Length; - /// - /// Returns a span around the contents of the builder. - /// - /// Ensures that the builder has a null char after - public ReadOnlySpan AsSpan(bool terminate) + if (_pos > (_chars.Length - count)) { - if (terminate) - { - EnsureCapacity(Length + 1); - _chars[Length] = '\0'; - } - return _chars.Slice(0, _pos); + Grow(count); } - public ReadOnlySpan AsSpan() => _chars.Slice(0, _pos); - public ReadOnlySpan AsSpan(int start) => _chars.Slice(start, _pos - start); - public ReadOnlySpan AsSpan(int start, int length) => _chars.Slice(start, length); + int remaining = _pos - index; + _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count)); + s +#if !NET + .AsSpan() +#endif + .CopyTo(_chars.Slice(index)); + _pos += count; + } - public bool TryCopyTo(Span destination, out int charsWritten) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(char c) + { + int pos = _pos; + Span chars = _chars; + if ((uint)pos < (uint)chars.Length) { - if (_chars.Slice(0, _pos).TryCopyTo(destination)) - { - charsWritten = _pos; - Dispose(); - return true; - } - else - { - charsWritten = 0; - Dispose(); - return false; - } + chars[pos] = c; + _pos = pos + 1; } - - public void Insert(int index, char value, int count) + else { - if (_pos > _chars.Length - count) - { - Grow(count); - } - - int remaining = _pos - index; - _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count)); - _chars.Slice(index, count).Fill(value); - _pos += count; + GrowAndAppend(c); } + } - public void Insert(int index, string? s) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(string? s) + { + if (s == null) { - if (s == null) - { - return; - } - - int count = s.Length; - - if (_pos > (_chars.Length - count)) - { - Grow(count); - } - - int remaining = _pos - index; - _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count)); - s -#if !NET - .AsSpan() -#endif - .CopyTo(_chars.Slice(index)); - _pos += count; + return; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Append(char c) + int pos = _pos; + if (s.Length == 1 && (uint)pos < (uint)_chars.Length) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc. { - int pos = _pos; - Span chars = _chars; - if ((uint)pos < (uint)chars.Length) - { - chars[pos] = c; - _pos = pos + 1; - } - else - { - GrowAndAppend(c); - } + _chars[pos] = s[0]; + _pos = pos + 1; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Append(string? s) + else { - if (s == null) - { - return; - } - - int pos = _pos; - if (s.Length == 1 && (uint)pos < (uint)_chars.Length) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc. - { - _chars[pos] = s[0]; - _pos = pos + 1; - } - else - { - AppendSlow(s); - } + AppendSlow(s); } + } - private void AppendSlow(string s) + private void AppendSlow(string s) + { + int pos = _pos; + if (pos > _chars.Length - s.Length) { - int pos = _pos; - if (pos > _chars.Length - s.Length) - { - Grow(s.Length); - } + Grow(s.Length); + } - s + s #if !NET - .AsSpan() + .AsSpan() #endif - .CopyTo(_chars.Slice(pos)); - _pos += s.Length; - } + .CopyTo(_chars.Slice(pos)); + _pos += s.Length; + } - public void Append(char c, int count) + public void Append(char c, int count) + { + if (_pos > _chars.Length - count) { - if (_pos > _chars.Length - count) - { - Grow(count); - } - - Span dst = _chars.Slice(_pos, count); - for (int i = 0; i < dst.Length; i++) - { - dst[i] = c; - } - _pos += count; + Grow(count); } - public void Append(scoped ReadOnlySpan value) + Span dst = _chars.Slice(_pos, count); + for (int i = 0; i < dst.Length; i++) { - int pos = _pos; - if (pos > _chars.Length - value.Length) - { - Grow(value.Length); - } - - value.CopyTo(_chars.Slice(_pos)); - _pos += value.Length; + dst[i] = c; } + _pos += count; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span AppendSpan(int length) + public void Append(scoped ReadOnlySpan value) + { + int pos = _pos; + if (pos > _chars.Length - value.Length) { - int origPos = _pos; - if (origPos > _chars.Length - length) - { - Grow(length); - } - - _pos = origPos + length; - return _chars.Slice(origPos, length); + Grow(value.Length); } - [MethodImpl(MethodImplOptions.NoInlining)] - private void GrowAndAppend(char c) + value.CopyTo(_chars.Slice(_pos)); + _pos += value.Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span AppendSpan(int length) + { + int origPos = _pos; + if (origPos > _chars.Length - length) { - Grow(1); - Append(c); + Grow(length); } - /// - /// Resize the internal buffer either by doubling current buffer size or - /// by adding to - /// whichever is greater. - /// - /// - /// Number of chars requested beyond current position. - /// - [MethodImpl(MethodImplOptions.NoInlining)] - private void Grow(int additionalCapacityBeyondPos) - { - Debug.Assert(additionalCapacityBeyondPos > 0); - Debug.Assert(_pos > _chars.Length - additionalCapacityBeyondPos, "Grow called incorrectly, no resize is needed."); + _pos = origPos + length; + return _chars.Slice(origPos, length); + } - const uint ArrayMaxLength = 0x7FFFFFC7; // same as Array.MaxLength + [MethodImpl(MethodImplOptions.NoInlining)] + private void GrowAndAppend(char c) + { + Grow(1); + Append(c); + } + + /// + /// Resize the internal buffer either by doubling current buffer size or + /// by adding to + /// whichever is greater. + /// + /// + /// Number of chars requested beyond current position. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private void Grow(int additionalCapacityBeyondPos) + { + Debug.Assert(additionalCapacityBeyondPos > 0); + Debug.Assert(_pos > _chars.Length - additionalCapacityBeyondPos, "Grow called incorrectly, no resize is needed."); - // Increase to at least the required size (_pos + additionalCapacityBeyondPos), but try - // to double the size if possible, bounding the doubling to not go beyond the max array length. - int newCapacity = (int)Math.Max( - (uint)(_pos + additionalCapacityBeyondPos), - Math.Min((uint)_chars.Length * 2, ArrayMaxLength)); + const uint ArrayMaxLength = 0x7FFFFFC7; // same as Array.MaxLength - // Make sure to let Rent throw an exception if the caller has a bug and the desired capacity is negative. - // This could also go negative if the actual required length wraps around. - char[] poolArray = ArrayPool.Shared.Rent(newCapacity); + // Increase to at least the required size (_pos + additionalCapacityBeyondPos), but try + // to double the size if possible, bounding the doubling to not go beyond the max array length. + int newCapacity = (int)Math.Max( + (uint)(_pos + additionalCapacityBeyondPos), + Math.Min((uint)_chars.Length * 2, ArrayMaxLength)); - _chars.Slice(0, _pos).CopyTo(poolArray); + // Make sure to let Rent throw an exception if the caller has a bug and the desired capacity is negative. + // This could also go negative if the actual required length wraps around. + char[] poolArray = ArrayPool.Shared.Rent(newCapacity); - char[]? toReturn = _arrayToReturnToPool; - _chars = _arrayToReturnToPool = poolArray; - if (toReturn != null) - { - ArrayPool.Shared.Return(toReturn); - } + _chars.Slice(0, _pos).CopyTo(poolArray); + + char[]? toReturn = _arrayToReturnToPool; + _chars = _arrayToReturnToPool = poolArray; + if (toReturn != null) + { + ArrayPool.Shared.Return(toReturn); } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dispose() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + char[]? toReturn = _arrayToReturnToPool; + this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again + if (toReturn != null) { - char[]? toReturn = _arrayToReturnToPool; - this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again - if (toReturn != null) - { - ArrayPool.Shared.Return(toReturn); - } + ArrayPool.Shared.Return(toReturn); } } } \ No newline at end of file