| File: src\aspnetcore\src\Components\Shared\src\ArrayBuilder.cs | Web Access |
| Project: ..\..\..\src\aspnetcore\src\Components\Components\src\Microsoft.AspNetCore.Components.csproj (Microsoft.AspNetCore.Components) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #nullable disable warnings using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; #if BLAZOR_WEBVIEW namespace Microsoft.AspNetCore.Components.WebView; #elif COMPONENTS_SERVER namespace Microsoft.AspNetCore.Components.Server.Circuits; #elif COMPONENTS_WEBASSEMBLY namespace Microsoft.AspNetCore.Components.WebAssembly.Rendering; #elif JS_INTEROP namespace Microsoft.JSInterop.Infrastructure; #else namespace Microsoft.AspNetCore.Components.RenderTree; #endif /// <summary> /// Implements a list that uses an array of objects to store the elements. /// /// This differs from a <see cref="System.Collections.Generic.List{T}"/> in that /// it not only grows as required but also shrinks if cleared with significant /// excess capacity. This makes it useful for component rendering, because /// components can be long-lived and re-render frequently, with the rendered size /// varying dramatically depending on the user's navigation in the app. /// </summary> #pragma warning disable CA1852 // Seal internal types internal class ArrayBuilder<T> : IDisposable #pragma warning restore CA1852 // Seal internal types { // The following fields are memory mapped to the WASM client. Do not re-order or use auto-properties. protected T[] _items; protected int _itemsInUse; private static readonly T[] Empty = Array.Empty<T>(); private readonly ArrayPool<T> _arrayPool; private readonly int _minCapacity; private bool _disposed; /// <summary> /// Constructs a new instance of <see cref="ArrayBuilder{T}"/>. /// </summary> public ArrayBuilder(int minCapacity = 32, ArrayPool<T> arrayPool = null) { _arrayPool = arrayPool ?? ArrayPool<T>.Shared; _minCapacity = minCapacity; _items = Empty; } /// <summary> /// Gets the number of items. /// </summary> public int Count => _itemsInUse; /// <summary> /// Gets the underlying buffer. /// </summary> public T[] Buffer => _items; /// <summary> /// Appends a new item, automatically resizing the underlying array if necessary. /// </summary> /// <param name="item">The item to append.</param> /// <returns>The index of the appended item.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] // Just like System.Collections.Generic.List<T> public int Append(in T item) { if (_itemsInUse == _items.Length) { GrowBuffer(_items.Length * 2); } var indexOfAppendedItem = _itemsInUse++; _items[indexOfAppendedItem] = item; return indexOfAppendedItem; } internal int Append(T[] source, int startIndex, int length) => Append(source.AsSpan(startIndex, length)); internal int Append(ReadOnlySpan<T> source) { // Expand storage if needed. Using same doubling approach as would // be used if you inserted the items one-by-one. var requiredCapacity = _itemsInUse + source.Length; if (_items.Length < requiredCapacity) { var candidateCapacity = Math.Max(_items.Length * 2, _minCapacity); while (candidateCapacity < requiredCapacity) { candidateCapacity *= 2; } GrowBuffer(candidateCapacity); } source.CopyTo(_items.AsSpan(_itemsInUse)); var startIndexOfAppendedItems = _itemsInUse; _itemsInUse += source.Length; return startIndexOfAppendedItems; } /// <summary> /// Sets the supplied value at the specified index. The index must be within /// range for the array. /// </summary> /// <param name="index">The index.</param> /// <param name="value">The value.</param> [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Overwrite(int index, in T value) { if (index > _itemsInUse) { ThrowIndexOutOfBoundsException(); } _items[index] = value; } /// <summary> /// Removes the last item. /// </summary> public void RemoveLast() { if (_itemsInUse == 0) { ThrowIndexOutOfBoundsException(); } _itemsInUse--; _items[_itemsInUse] = default; // Release to GC } /// <summary> /// Inserts the item at the specified index, moving the contents of the subsequent entries along by one. /// </summary> /// <param name="index">The index at which the value is to be inserted.</param> /// <param name="value">The value to insert.</param> public void InsertExpensive(int index, T value) { if (index > _itemsInUse) { ThrowIndexOutOfBoundsException(); } if (_itemsInUse == _items.Length) { GrowBuffer(_items.Length * 2); } Array.Copy(_items, index, _items, index + 1, _itemsInUse - index); _itemsInUse++; _items[index] = value; } /// <summary> /// Marks the array as empty, also shrinking the underlying storage if it was /// not being used to near its full capacity. /// </summary> public void Clear() { ReturnBuffer(); _items = Empty; _itemsInUse = 0; } protected void GrowBuffer(int desiredCapacity) { // When we dispose, we set the count back to zero and return the array. // // If someone tries to do something that would require non-zero storage then // this is a use-after-free. Throwing here is an easy way to prevent that without // introducing overhead to every method. ObjectDisposedException.ThrowIf(_disposed, null); var newCapacity = Math.Max(desiredCapacity, _minCapacity); Debug.Assert(newCapacity > _items.Length); var newItems = _arrayPool.Rent(newCapacity); Array.Copy(_items, newItems, _itemsInUse); // Return the old buffer and start using the new buffer ReturnBuffer(); _items = newItems; } private void ReturnBuffer() { if (!ReferenceEquals(_items, Empty)) { // ArrayPool<>.Return with clearArray: true calls Array.Clear on the entire buffer. // In the most common case, _itemsInUse would be much smaller than _items.Length so we'll specifically clear that subset. Array.Clear(_items, 0, _itemsInUse); _arrayPool.Return(_items); } } public void Dispose() { if (!_disposed) { _disposed = true; ReturnBuffer(); _items = Empty; _itemsInUse = 0; } } private static void ThrowIndexOutOfBoundsException() { throw new ArgumentOutOfRangeException("index"); } }