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
来实现这一目的,但这很容易出错,因此不建议这样做。
示例
可以在单元测试中查找更多示例。
反馈
https://aka.ms/ContentUserFeedback。
即将发布:在整个 2024 年,我们将逐步淘汰作为内容反馈机制的“GitHub 问题”,并将其取代为新的反馈系统。 有关详细信息,请参阅:提交和查看相关反馈