| File: BufferWriterPool\BufferWriter.cs | Web Access |
| Project: src\src\Shared\Shared.csproj (Shared) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Buffers; using Microsoft.Shared.Diagnostics; #if NETCOREAPP3_1_OR_GREATER using System.Runtime.CompilerServices; #endif #pragma warning disable CA1716 namespace Microsoft.Shared.Pools; #pragma warning restore CA1716 /// <summary> /// Represents an output sink into which <typeparamref name="T"/> data can be written. /// </summary> /// <remarks> /// This class is similar to <c>System.Buffers.ArrayBufferWriter<T></c>, with the exception that the /// <c>ArrayBufferWriter<T>.Clear</c> method has been replaced with a <see cref="Reset"/> /// method. When used with value types, <see cref="Reset"/> doesn't clear the underlying memory /// buffer to <c>default(T)</c>, which makes it considerably faster. Additionally, this class /// lets you explicitly set the capacity of the underlying buffer. /// </remarks> /// <typeparam name="T">The type of value that will be written into the writer.</typeparam> #if !SHARED_PROJECT [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] #endif internal sealed class BufferWriter<T> : IBufferWriter<T> { internal const int MaxArrayLength = 0X7FEF_FFFF; // Copy of the internal Array.MaxArrayLength const private const int DefaultCapacity = 256; private T[] _buffer = Array.Empty<T>(); /// <summary> /// Gets the data written to the underlying buffer so far. /// </summary> /// <remarks> /// The returned value can become stale whenever the buffer is written to again. As such, you should /// always call this property in order to get a fresh value before reading from the buffer. /// </remarks> public ReadOnlyMemory<T> WrittenMemory => _buffer.AsMemory(0, WrittenCount); /// <summary> /// Gets the data written to the underlying buffer so far. /// </summary> /// <remarks> /// The returned span can become stale whenever the buffer is written to again. As such, you should /// always call this property in order to get a fresh span before reading from the buffer. /// </remarks> public ReadOnlySpan<T> WrittenSpan => _buffer.AsSpan(0, WrittenCount); /// <summary> /// Gets the amount of data written to the underlying buffer so far. /// </summary> public int WrittenCount { get; private set; } /// <summary> /// Gets or sets the total amount of space within the underlying buffer. /// </summary> /// <remarks> /// When reducing the capacity, the value of <see cref="WrittenCount" /> is clamped to the /// new capacity. /// </remarks> public int Capacity { get => _buffer.Length; set { _ = Throw.IfLessThan(value, 0); Array.Resize(ref _buffer, value); if (WrittenCount > value) { WrittenCount = value; } } } /// <summary> /// Clears the data written to the underlying buffer. /// </summary> /// <remarks> /// You must reset the <see cref="BufferWriter{T}"/> before trying to re-use it. /// If <typeparamref name="T"/> is or contains reference types, than this method /// will clear the underlying memory buffers to default(T) in order to ensure the /// GC is able to reclaim references correctly. If <typeparamref name="T"/> /// is a value type and only contains value types, then this method doesn't /// clear memory and so completes considerably faster. /// </remarks> public void Reset() { #if NETCOREAPP3_1_OR_GREATER if (RuntimeHelpers.IsReferenceOrContainsReferences<T>()) { _buffer.AsSpan(0, WrittenCount).Clear(); } #else _buffer.AsSpan(0, WrittenCount).Clear(); #endif WrittenCount = 0; } /// <summary> /// Notifies <see cref="IBufferWriter{T}"/> that <paramref name="count"/> amount of data was written to the output <see cref="Span{T}"/>/<see cref="Memory{T}"/>. /// </summary> /// <param name="count">The amount of data that has been consumed.</param> /// <exception cref="ArgumentOutOfRangeException"> /// Thrown when <paramref name="count"/> is negative or when attempting to advance past the end of the underlying buffer. /// </exception> /// <remarks> /// You must request a new buffer after calling <see cref="Advance"/> to continue writing more data and cannot write to a previously acquired buffer. /// </remarks> public void Advance(int count) { _ = Throw.IfOutOfRange(count, 0, _buffer.Length - WrittenCount); WrittenCount += count; } /// <summary> /// Returns a <see cref="Memory{T}"/> to write to that is at least the requested length (specified by <paramref name="sizeHint"/>). /// </summary> /// <exception cref="ArgumentException"> /// Thrown when <paramref name="sizeHint"/> is negative. /// </exception> /// <remarks> /// This will never return an empty <see cref="Memory{T}"/>. /// There is no guarantee that successive calls will return the same buffer or the same-sized buffer. /// You must request a new buffer after calling <see cref="Advance"/> to continue writing more data and cannot write to a previously acquired buffer. /// </remarks> /// <param name="sizeHint">THe minimum size of the returned buffer. If this is 0, returns a buffer with at least 1 byte available.</param> /// <returns>A block of memory that can be written to.</returns> public Memory<T> GetMemory(int sizeHint = 0) { EnsureCapacity(sizeHint); return _buffer.AsMemory(WrittenCount); } /// <summary> /// Returns a <see cref="Span{T}"/> to write to that is at least the requested length (specified by <paramref name="sizeHint"/>). /// </summary> /// <exception cref="ArgumentOutOfRangeException"> /// Thrown when <paramref name="sizeHint"/> is negative. /// </exception> /// <remarks> /// This will never return an empty <see cref="Span{T}"/>. /// There is no guarantee that successive calls will return the same buffer or the same-sized buffer. /// You must request a new buffer after calling <see cref="Advance"/> to continue writing more data and cannot write to a previously acquired buffer. /// </remarks> /// <param name="sizeHint">THe minimum size of the returned buffer. If this is 0, returns a buffer with at least 1 byte available.</param> /// <returns>A block of memory that can be written to.</returns> public Span<T> GetSpan(int sizeHint = 0) { EnsureCapacity(sizeHint); return _buffer.AsSpan(WrittenCount); } private void EnsureCapacity(int sizeHint) { if (sizeHint == 0) { sizeHint = 1; } var avail = _buffer.Length - WrittenCount; if (sizeHint > avail) { var targetCapacity = _buffer.Length == 0 ? DefaultCapacity : _buffer.Length * 2; if (targetCapacity - WrittenCount < sizeHint) { targetCapacity = WrittenCount + sizeHint; } if ((uint)targetCapacity > MaxArrayLength) { Throw.InvalidOperationException("Exceeded array capacity"); } Array.Resize(ref _buffer, targetCapacity); } else { _ = Throw.IfLessThan(sizeHint, 0); } } }