通过.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的目标,封装后的使用只需一行代码,效果如下:
using var pool = new ArrayPoolWrapper<int>(5);
具体实现代码如下:
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>)); } } }
我们还可以通过封装来实现更多的扩展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上开源。