mirror of
https://github.com/chylex/.NET-Community-Toolkit.git
synced 2024-11-25 01:42:46 +01:00
202 lines
8.9 KiB
C#
202 lines
8.9 KiB
C#
// Licensed to the .NET Foundation under one or more agreements.
|
|
// The .NET Foundation licenses this file to you under the MIT license.
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
using System;
|
|
using System.Buffers;
|
|
using System.Diagnostics;
|
|
using System.Runtime.CompilerServices;
|
|
#if NETCOREAPP3_1_OR_GREATER
|
|
using System.Runtime.InteropServices;
|
|
#endif
|
|
using CommunityToolkit.HighPerformance.Buffers.Views;
|
|
|
|
namespace CommunityToolkit.HighPerformance.Buffers;
|
|
|
|
/// <summary>
|
|
/// A stack-only type with the ability to rent a buffer of a specified length and getting a <see cref="Span{T}"/> from it.
|
|
/// This type mirrors <see cref="MemoryOwner{T}"/> but without allocations and with further optimizations.
|
|
/// As this is a stack-only type, it relies on the duck-typed <see cref="IDisposable"/> pattern introduced with C# 8.
|
|
/// It should be used like so:
|
|
/// <code>
|
|
/// using (SpanOwner<byte> buffer = SpanOwner<byte>.Allocate(1024))
|
|
/// {
|
|
/// // Use the buffer here...
|
|
/// }
|
|
/// </code>
|
|
/// As soon as the code leaves the scope of that <see langword="using"/> block, the underlying buffer will automatically
|
|
/// be disposed. The APIs in <see cref="SpanOwner{T}"/> rely on this pattern for extra performance, eg. they don't perform
|
|
/// the additional checks that are done in <see cref="MemoryOwner{T}"/> to ensure that the buffer hasn't been disposed
|
|
/// before returning a <see cref="Memory{T}"/> or <see cref="Span{T}"/> instance from it.
|
|
/// As such, this type should always be used with a <see langword="using"/> block or expression.
|
|
/// Not doing so will cause the underlying buffer not to be returned to the shared pool.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of items to store in the current instance.</typeparam>
|
|
[DebuggerTypeProxy(typeof(MemoryDebugView<>))]
|
|
[DebuggerDisplay("{ToString(),raw}")]
|
|
public readonly ref struct SpanOwner<T>
|
|
{
|
|
#pragma warning disable IDE0032
|
|
/// <summary>
|
|
/// The usable length within <see cref="array"/>.
|
|
/// </summary>
|
|
private readonly int length;
|
|
#pragma warning restore IDE0032
|
|
|
|
/// <summary>
|
|
/// The <see cref="ArrayPool{T}"/> instance used to rent <see cref="array"/>.
|
|
/// </summary>
|
|
private readonly ArrayPool<T> pool;
|
|
|
|
/// <summary>
|
|
/// The underlying <typeparamref name="T"/> array.
|
|
/// </summary>
|
|
private readonly T[] array;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="SpanOwner{T}"/> struct with the specified parameters.
|
|
/// </summary>
|
|
/// <param name="length">The length of the new memory buffer to use.</param>
|
|
/// <param name="pool">The <see cref="ArrayPool{T}"/> instance to use.</param>
|
|
/// <param name="mode">Indicates the allocation mode to use for the new buffer to rent.</param>
|
|
private SpanOwner(int length, ArrayPool<T> pool, AllocationMode mode)
|
|
{
|
|
this.length = length;
|
|
this.pool = pool;
|
|
this.array = pool.Rent(length);
|
|
|
|
if (mode == AllocationMode.Clear)
|
|
{
|
|
this.array.AsSpan(0, length).Clear();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets an empty <see cref="SpanOwner{T}"/> instance.
|
|
/// </summary>
|
|
public static SpanOwner<T> Empty
|
|
{
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
get => new(0, ArrayPool<T>.Shared, AllocationMode.Default);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new <see cref="SpanOwner{T}"/> instance with the specified parameters.
|
|
/// </summary>
|
|
/// <param name="size">The length of the new memory buffer to use.</param>
|
|
/// <returns>A <see cref="SpanOwner{T}"/> instance of the requested length.</returns>
|
|
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="size"/> is not valid.</exception>
|
|
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static SpanOwner<T> Allocate(int size) => new(size, ArrayPool<T>.Shared, AllocationMode.Default);
|
|
|
|
/// <summary>
|
|
/// Creates a new <see cref="SpanOwner{T}"/> instance with the specified parameters.
|
|
/// </summary>
|
|
/// <param name="size">The length of the new memory buffer to use.</param>
|
|
/// <param name="pool">The <see cref="ArrayPool{T}"/> instance to use.</param>
|
|
/// <returns>A <see cref="SpanOwner{T}"/> instance of the requested length.</returns>
|
|
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="size"/> is not valid.</exception>
|
|
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static SpanOwner<T> Allocate(int size, ArrayPool<T> pool) => new(size, pool, AllocationMode.Default);
|
|
|
|
/// <summary>
|
|
/// Creates a new <see cref="SpanOwner{T}"/> instance with the specified parameters.
|
|
/// </summary>
|
|
/// <param name="size">The length of the new memory buffer to use.</param>
|
|
/// <param name="mode">Indicates the allocation mode to use for the new buffer to rent.</param>
|
|
/// <returns>A <see cref="SpanOwner{T}"/> instance of the requested length.</returns>
|
|
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="size"/> is not valid.</exception>
|
|
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static SpanOwner<T> Allocate(int size, AllocationMode mode) => new(size, ArrayPool<T>.Shared, mode);
|
|
|
|
/// <summary>
|
|
/// Creates a new <see cref="SpanOwner{T}"/> instance with the specified parameters.
|
|
/// </summary>
|
|
/// <param name="size">The length of the new memory buffer to use.</param>
|
|
/// <param name="pool">The <see cref="ArrayPool{T}"/> instance to use.</param>
|
|
/// <param name="mode">Indicates the allocation mode to use for the new buffer to rent.</param>
|
|
/// <returns>A <see cref="SpanOwner{T}"/> instance of the requested length.</returns>
|
|
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="size"/> is not valid.</exception>
|
|
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static SpanOwner<T> Allocate(int size, ArrayPool<T> pool, AllocationMode mode) => new(size, pool, mode);
|
|
|
|
/// <summary>
|
|
/// Gets the number of items in the current instance
|
|
/// </summary>
|
|
public int Length
|
|
{
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
get => this.length;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a <see cref="Span{T}"/> wrapping the memory belonging to the current instance.
|
|
/// </summary>
|
|
public Span<T> Span
|
|
{
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
get
|
|
{
|
|
#if NETCOREAPP3_1_OR_GREATER
|
|
ref T r0 = ref this.array!.DangerousGetReference();
|
|
|
|
return MemoryMarshal.CreateSpan(ref r0, this.length);
|
|
#else
|
|
return new(this.array, 0, this.length);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a reference to the first element within the current instance, with no bounds check.
|
|
/// </summary>
|
|
/// <returns>A reference to the first element within the current instance.</returns>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public ref T DangerousGetReference()
|
|
{
|
|
return ref this.array.DangerousGetReference();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets an <see cref="ArraySegment{T}"/> instance wrapping the underlying <typeparamref name="T"/> array in use.
|
|
/// </summary>
|
|
/// <returns>An <see cref="ArraySegment{T}"/> instance wrapping the underlying <typeparamref name="T"/> array in use.</returns>
|
|
/// <remarks>
|
|
/// This method is meant to be used when working with APIs that only accept an array as input, and should be used with caution.
|
|
/// In particular, the returned array is rented from an array pool, and it is responsibility of the caller to ensure that it's
|
|
/// not used after the current <see cref="SpanOwner{T}"/> instance is disposed. Doing so is considered undefined behavior,
|
|
/// as the same array might be in use within another <see cref="SpanOwner{T}"/> instance.
|
|
/// </remarks>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public ArraySegment<T> DangerousGetArray()
|
|
{
|
|
return new(this.array!, 0, this.length);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Implements the duck-typed <see cref="IDisposable.Dispose"/> method.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void Dispose()
|
|
{
|
|
this.pool.Return(this.array);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override string ToString()
|
|
{
|
|
if (typeof(T) == typeof(char) &&
|
|
this.array is char[] chars)
|
|
{
|
|
return new(chars, 0, this.length);
|
|
}
|
|
|
|
// Same representation used in Span<T>
|
|
return $"CommunityToolkit.HighPerformance.Buffers.SpanOwner<{typeof(T)}>[{this.length}]";
|
|
}
|
|
}
|