SpanOwner<T>
The SpanOwner<T>
is a stack-only buffer type that rents buffers from a shared memory pool. It essentially mirrors the functionality of MemoryOwner<T>
, but as a ref struct
type. This is particularly useful for short-lived buffers that are only used in synchronous code (that don't require Memory<T>
instances), as well as code running in a tight loop, as creating SpanOwner<T>
values will not require memory allocations at all.
Platform APIs:
SpanOwner<T>
,MemoryOwner<T>
Syntax
The same core features of MemoryOwner<T>
apply to this type as well, with the exception of it being a stack-only struct
, and the fact that it lacks the IMemoryOwner<T>
interface
implementation, as well as the Memory<T>
property. The syntax is virtually identical to that used with MemoryOwner<T>
as well, except for the differences mentioned above.
As an example, suppose we have a method where we need to allocate a temporary buffer of a specified size (let's call this value length
), and then use it to perform some work. A first, inefficient version might look like this:
byte[] buffer = new byte[length];
// Use buffer here
This is not ideal, as we're allocating a new buffer every time this code is used, and then throwing it away immediately (as mentioned in the MemoryOwner<T>
docs as well), which puts more pressure on the garbage collector. We can optimize the code above by using 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);
}
The code above rents a buffer from an array pool, but it's more verbose and error-prone: we need to be careful with the try/finally
block to make sure to always return the rented buffer to the pool. We can rewrite it by using the SpanOwner<T>
type, like so:
// 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
The SpanOwner<T>
instance will internally rent an array, and will take care of returning it to the pool when it goes out of scope. We no longer need to use a try/finally
block either, as the C# compiler will add that automatically when expanding that using
statement. As such, the SpanOwner<T>
type can be seen as a lightweight wrapper around the ArrayPool<T>
APIs, which makes them both more compact and easier to use, reducing the amount of code that needs to be written to properly rent and dispose short lived buffers. You can see how using SpanOwner<T>
makes the code much shorter and more straightforward.
Note
As this is a stack-only type, it relies on the duck-typed IDisposable
pattern introduced with C# 8. That is shown in the sample above: the SpanOwner<T>
type is being used within a using
block despite the fact that the type doesn't implement the IDisposable
interface at all, and it's also never boxed. The functionality is just the same: as soon as the buffer goes out of scope, it is automatically disposed. The APIs in SpanOwner{T}
rely on this pattern for extra performance: they assume that the underlying buffer will never be disposed as long as the SpanOwner<T>
type is in scope, and they don't perform the additional checks that are done in MemoryOwner<T>
to ensure that the buffer is in fact still available before returning a Memory<T>
or Span<T>
instance from it. As such, this type should always be used with a using
block or expression. Not doing so will cause the underlying buffer not to be returned to the shared pool. Technically the same can also be achieved by manually calling Dispose
on the SpanOwner<T>
type (which doesn't require C# 8), but that is error prone and hence not recommended.
Examples
You can find more examples in the unit tests.
.NET Community Toolkit