SpanOwner<T>
El SpanOwner<T>
es un tipo de búfer de solo pila que alquila búferes de un grupo de memoria compartido. Básicamente refleja la funcionalidad de MemoryOwner<T>
, pero como un tipo ref struct
. Esto es especialmente útil para los búferes de corta duración que solo se usan en código sincrónico (que no requieren instancias de Memory<T>
), así como código que se ejecuta en un bucle ajustado, ya que la creación de valores SpanOwner<T>
no requerirá asignaciones de memoria en absoluto.
API de la plataforma:
SpanOwner<T>
,MemoryOwner<T>
Sintaxis
Las mismas características principales de MemoryOwner<T>
también se aplican a este tipo, a excepción de que sea struct
de solo pila, y el hecho de que carece de la implementación IMemoryOwner<T>
interface
, así como la propiedad Memory<T>
. La sintaxis es prácticamente idéntica a la que se usa con MemoryOwner<T>
, excepto las diferencias mencionadas anteriormente.
Por ejemplo, supongamos que tenemos un método en el que necesitamos asignar un búfer temporal de un tamaño especificado (vamos a llamar a este valor length
) y, a continuación, usarlo para realizar algún trabajo. Una primera versión ineficaz podría tener este aspecto:
byte[] buffer = new byte[length];
// Use buffer here
Esto no es ideal, ya que asignamos un nuevo búfer cada vez que se usa este código y, a continuación, lo elimina inmediatamente (como se menciona en los documentos MemoryOwner<T>
), lo que pone más presión en el recolector de elementos no utilizados. Podemos optimizar el código anterior mediante 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);
}
El código anterior alquila un búfer de un grupo de matrices, pero es más detallado y propenso a errores: es necesario tener cuidado con el bloque try/finally
para asegurarse de devolver siempre el búfer alquilado al grupo. Podemos volver a escribirlo mediante el tipo SpanOwner<T>
, de la siguiente manera:
// 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
La instancia SpanOwner<T>
alquilará internamente una matriz y se encargará de devolverla al grupo cuando salga del ámbito. Ya no es necesario usar un bloque try/finally
, ya que el compilador de C# lo agregará automáticamente al expandir esa instrucción using
. Por lo tanto, el tipo SpanOwner<T>
se puede ver como un contenedor ligero alrededor de las API ArrayPool<T>
, lo que hace que sean más compactos y fáciles de usar, lo que reduce la cantidad de código que se debe escribir para alquilar y eliminar búferes de corta duración correctamente. Puede ver cómo el uso SpanOwner<T>
hace que el código sea mucho más corto y sencillo.
Nota:
Como se trata de un tipo de solo pila, se basa en el patrón IDisposable
tipo duck introducido con C# 8. Esto se muestra en el ejemplo anterior: el tipo SpanOwner<T>
se usa dentro de un bloque using
a pesar del hecho de que el tipo no implementa la interfaz IDisposable
en absoluto y tampoco está boxeado. La funcionalidad es igual: en cuanto el búfer sale del ámbito, se elimina automáticamente. Las API SpanOwner{T}
dependen de este patrón para obtener un rendimiento adicional: suponen que el búfer subyacente nunca se eliminará siempre que el tipo SpanOwner<T>
esté en el ámbito y no realice las comprobaciones adicionales que se realizan en MemoryOwner<T>
para asegurarse de que el búfer todavía está disponible antes de devolver una instancia de Memory<T>
o Span<T>
de ella. Por lo tanto, este tipo siempre debe usarse con un bloque o expresión using
. Si no lo hace, el búfer subyacente no se devolverá al grupo compartido. Técnicamente, también se puede lograr mediante una llamada manual Dispose
en el tipoSpanOwner<T>
(que no requiere C# 8), pero es propenso a errores y, por tanto, no se recomienda.
Ejemplos
Encontrará más ejemplos en las pruebas unitarias.
.NET Community Toolkit