SpanOwner<T>

SpanOwner<T> 是一种从共享内存池租用缓冲区的仅堆栈缓冲区类型。 它实质上具有与 MemoryOwner<T> 相同的功能,但属于是 ref struct 类型。 这对于仅在同步代码中使用的短生存期缓冲区(无需 Memory<T> 实例)以及在紧密循环中运行的代码特别有用,因为创建 SpanOwner<T> 值根本不需要内存分配。

平台 API:SpanOwner<T>MemoryOwner<T>

语法

MemoryOwner<T> 的核心功能也适用于此类型,只不过它是仅堆栈 struct,并且它缺少 IMemoryOwner<T>interface 实现以及 Memory<T> 属性。 除了上述差异外,语法也与 MemoryOwner<T> 使用的语法几乎相同。

例如,在某个方法中我们需要分配一个指定大小的临时缓冲区(不妨将此值称为 length),然后用它来执行一些工作。 最初的效率较低的版本可能与下面所示的类似:

byte[] buffer = new byte[length];

// Use buffer here

这并不理想,因为我们每次使用此代码时都会分配新的缓冲区,然后立即将其丢弃(如 MemoryOwner<T> 文档所述),这给垃圾回收器带来了更大的压力。 可以使用 ArrayPool<T> 来优化此代码:

// Using directive to access the ArrayPool<T> type
using System.Buffers;

int[] buffer = ArrayPool<int>.Shared.Rent(length);

try
{
    // Slice the span, as it might be larger than the requested size
    Span<int> span = buffer.AsSpan(0, length);

    // Use the span here
}
finally
{
    ArrayPool<int>.Shared.Return(buffer);
}

上述代码从数组池中租用缓冲区,但它更繁琐且更容易出错:使用 try/finally 块时需要谨慎,以确保始终将租用的缓冲区返回到池中。 可以使用 SpanOwner<T> 类型重写它,如下所示:

// Be sure to include this using at the top of the file:
using Microsoft.Toolkit.HighPerformance.Buffers;

using SpanOwner<int> buffer = SpanOwner<int>.Allocate(length);

Span<int> span = buffer.Span;

// Use the span here, no slicing necessary

SpanOwner<T> 实例将在内部租用数组,并在超出范围时负责将其返回到池中。 我们也无需再使用 try/finally 块,因为 C# 编译器将在扩展该 using 语句时自动添加该块。 因此,可将 SpanOwner<T> 类型视为 ArrayPool<T> API 的轻量级包装器,这使得它们更紧凑、更易于使用,减少了为正确租用和释放短期生存期缓冲区而需要编写的代码量。 你可以看到,通过使用 SpanOwner<T>,代码是如何变得更短、更简单明了。

注意

由于这是仅堆栈类型,它依赖于 C# 8 中引入的鸭子类型 IDisposable 模式。 如以上示例所示:尽管 SpanOwner<T> 类型根本不实现 IDisposable 接口,并且也从不装箱,但它仍可在 using 块中使用。 此功能完全相同:一旦缓冲区超出范围,它就会自动释放。 SpanOwner{T} 中的 API 依赖于此模式来获得额外的性能:它们假定只要 SpanOwner<T> 类型在范围内,就永远不会释放基础缓冲区,并且它们不会执行在 MemoryOwner<T> 中执行的附加检查,以确保缓冲区在从中返回 Memory<T>Span<T> 实例之前实际上仍然可用。 因此,此类型应始终与 using 块或表达式一起使用。 不遵循此方法将导致基础缓冲区不会被返回到共享池。 从技术上讲,也可以通过在 SpanOwner<T> 类型(无需 C# 8)上手动调用 Dispose 来实现这一目的,但这很容易出错,因此不建议这样做。

示例

可以在单元测试中查找更多示例。