通过.NET中的 ArrayPool 我们可以实现对T[]类型的池化,避免频繁的分配内存和GC,以提升性能。鉴于已有不少博客介绍ArrayPool的具体原理,本文不会涉及其实现细节。本文聚焦使用中的痛点,并提供简洁的封装方案以提升ArrarPool使用的便捷性。
ArrayPool本身的使用方式比较简单:
using System.Buffers;
var pool = ArrayPool<int>.Shared.Rent(4);
// 其他逻辑
ArrayPool<int>.Shared.Return(pool);
为了确保在发生异常时能够释放资源,通常需要写成如下形式的样板代码:
int[] pool = null!;
try
{
pool = ArrayPool<int>.Shared.Rent(4);
// 其他逻辑
}
finally
{
if (pool != null)
{
ArrayPool<int>.Shared.Return(pool);
}
}
以上写法会是我们的代码中充斥大量的样板代码和大量的嵌套,影响代码后续的可读性和可维护性。
接下来我们在原ArrayPool的基础上稍加封装,以实现简洁、安全的使用ArrayPool的目标,代码如下:
public struct ArrayPoolWrapper<T> : IDisposable
{
private int _index = -1;
private bool _disposed = false;
private readonly int _capacity;
private readonly T[] _pool;
public ArrayPoolWrapper(int capacity)
{
if (capacity <= 0)
{
throw new ArgumentOutOfRangeException(nameof(capacity), "The capacity must be greater than 0.");
}
this._capacity = capacity;
_pool = ArrayPool<T>.Shared.Rent(capacity);
}
public void Add(T info)
{
ThrowIfDisposed();
_index++;
if (_index >= _capacity)
{
_index--;
throw new InvalidOperationException("The array pool has reached its capacity.");
}
_pool[_index] = info;
}
public void Dispose()
{
ThrowIfDisposed();
_disposed = true;
ArrayPool<T>.Shared.Return(_pool);
}
private readonly void ThrowIfDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(ArrayPoolWrapper<T>));
}
}
}
封装后的使用只需一行代码,效果如下:
using var pool = new ArrayPoolWrapper<int>(5);
我们还可以通过封装来实现更多的扩展API,如:RemoveLastOne以及基于Span的切片操作:
public struct ArrayPoolWrapper<T> : IDisposable
{
public readonly int Count => _index + 1;
public readonly Span<T> Values => _pool.AsSpan()[..Count];
public void RemoveLastOne()
{
ThrowIfDisposed();
if (Count <= 0)
{
throw new InvalidOperationException("The array pool is empty.");
}
_pool[_index] = default!;
_index--;
}
}
使用示例如下:
using var pool = new ArrayPoolWrapper<int>(8);
for (var i = 0; i < 8; i++)
{
pool.Add(i);
}
pool.RemoveLastOne();
Console.WriteLine(pool.Count);
foreach (var i in pool.Values[1..3])
{
Console.WriteLine(i);
}
完整的实现代码已在Github上开源。