| File: Utilities\BufferScope.cs | Web Access |
| Project: src\msbuild\src\Framework\Microsoft.Build.Framework.csproj (Microsoft.Build.Framework) |
// 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 System.Diagnostics; namespace Microsoft.Build.Utilities; /// <summary> /// Allows renting a buffer from <see cref="ArrayPool{T}"/> with a using statement. Can be used directly as if it /// were a <see cref="Span{T}"/>. /// </summary> internal ref struct BufferScope<T> { private T[]? _array; private Span<T> _span; /// <summary> /// Initializes a new instance of the <see cref="BufferScope{T}"/>. /// </summary> /// <param name="minimumLength">The required minimum length.</param> public BufferScope(int minimumLength) { _array = ArrayPool<T>.Shared.Rent(minimumLength); _span = _array; } /// <summary> /// Create the <see cref="BufferScope{T}"/> with an initial buffer. Useful for creating with an initial stack /// allocated buffer. /// </summary> /// <param name="initialBuffer">The initial buffer to use.</param> public BufferScope(Span<T> initialBuffer) { _array = null; _span = initialBuffer; } /// <summary> /// Create the <see cref="BufferScope{T}"/> with an initial buffer. Useful for creating with an initial stack /// allocated buffer. /// </summary> /// <remarks> /// <example> /// Creating with a stack allocated buffer: /// <code>using BufferScope<char> buffer = new(stackalloc char[64]);</code> /// </example> /// </remarks> /// <param name="initialBuffer"> /// The initial buffer to use. If not large enough for <paramref name="minimumLength"/>, a buffer will be rented /// from the shared <see cref="ArrayPool{T}"/>. /// </param> /// <param name="minimumLength"> /// The required minimum length. If the <paramref name="initialBuffer"/> is not large enough, this will rent from /// the shared <see cref="ArrayPool{T}"/>. /// </param> public BufferScope(Span<T> initialBuffer, int minimumLength) { if (initialBuffer.Length >= minimumLength) { _array = null; _span = initialBuffer; } else { _array = ArrayPool<T>.Shared.Rent(minimumLength); _span = _array; } } /// <summary> /// Ensure that the buffer has enough space for <paramref name="capacity"/> number of elements. /// </summary> /// <remarks> /// <para> /// Consider if creating new <see cref="BufferScope{T}"/> instances is possible and cleaner than using /// this method. /// </para> /// </remarks> /// <param name="capacity">The minimum number of elements the buffer should be able to hold.</param> /// <param name="copy">True to copy the existing elements when new space is allocated.</param> public void EnsureCapacity(int capacity, bool copy = false) { if (_span.Length >= capacity) { return; } // Keep method separate for better inlining. IncreaseCapacity(capacity, copy); } private void IncreaseCapacity(int capacity, bool copy) { Debug.Assert(capacity > _span.Length); T[] newArray = ArrayPool<T>.Shared.Rent(capacity); if (copy) { _span.CopyTo(newArray); } if (_array is not null) { ArrayPool<T>.Shared.Return(_array, clearArray: TypeInfo<T>.IsReferenceOrContainsReferences()); } _array = newArray; _span = _array; } /// <summary> /// Gets or sets the element at the specified index. /// </summary> /// <param name="i">The zero-based index of the element to get or set.</param> /// <returns>The element at the specified index.</returns> public ref T this[int i] => ref _span[i]; /// <summary> /// Forms a slice out of the buffer starting at a specified index for a specified length. /// </summary> /// <param name="start">The index at which to begin the slice.</param> /// <param name="length">The desired length of the slice.</param> /// <returns>A span that consists of <paramref name="length"/> elements from the buffer starting at <paramref name="start"/>.</returns> public readonly Span<T> Slice(int start, int length) => _span.Slice(start, length); /// <inheritdoc cref="Span{T}.GetPinnableReference"/> /// <remarks> /// This is used by C# to enable using the buffer in a fixed statement. /// </remarks> public readonly ref T GetPinnableReference() => ref _span.GetPinnableReference(); /// <summary> /// Gets the number of elements in the buffer. /// </summary> public readonly int Length => _span.Length; /// <summary> /// Returns the buffer as a <see cref="Span{T}"/>. /// </summary> /// <returns>A span that represents the buffer.</returns> public readonly Span<T> AsSpan() => _span; /// <summary> /// Implicitly converts a <see cref="BufferScope{T}"/> to a <see cref="Span{T}"/>. /// </summary> /// <param name="scope">The buffer scope to convert.</param> /// <returns>A span that represents the buffer.</returns> public static implicit operator Span<T>(BufferScope<T> scope) => scope._span; /// <summary> /// Implicitly converts a <see cref="BufferScope{T}"/> to a <see cref="ReadOnlySpan{T}"/>. /// </summary> /// <param name="scope">The buffer scope to convert.</param> /// <returns>A read-only span that represents the buffer.</returns> public static implicit operator ReadOnlySpan<T>(BufferScope<T> scope) => scope._span; /// <summary> /// Returns an enumerator for the buffer's backing <see cref="Span{T}"/>. /// </summary> public readonly Span<T>.Enumerator GetEnumerator() => _span.GetEnumerator(); /// <summary> /// Releases the rented array back to the <see cref="ArrayPool{T}"/> if one was rented. /// </summary> public void Dispose() { // Clear the span to avoid accidental use after returning the array. _span = default; if (_array is not null) { ArrayPool<T>.Shared.Return(_array, clearArray: TypeInfo<T>.IsReferenceOrContainsReferences()); } _array = null; } /// <summary> /// Returns a string representation of the buffer. /// </summary> /// <returns>A string representation of the buffer.</returns> public override readonly string ToString() => _span.ToString(); }