ArrayPoolから借りてくるToArray
以前の記事で触れた、UniTaskにあったArrayPoolUtilをほぼパクって汎用化させたもの。
/// <summary> /// 生成する配列をArrayPoolから借りてくる /// </summary> public static class ArrayPoolUtility { /// <summary> /// ToArrayの感じで、生成する配列をArrayPoolから借りてくる /// </summary> /// <param name="source">イテレータ</param> /// <typeparam name="T">要素の型</typeparam> /// <returns>ArrayPoolから借りたArrayのラッパー</returns> public static DisposableRentArray<T> ToDisposableRentArray<T>(this IEnumerable<T> source) { // CopyToが可能だったり空っぽだとわかる場合は直接要素数から生成する switch (source) { case T[] array: return new DisposableRentArray<T>(array, shouldReturnPool: false); case ICollection<T> { Count: 0 }: return new DisposableRentArray<T>(Array.Empty<T>(), shouldReturnPool: false); case IReadOnlyCollection<T> { Count: 0 } : return new DisposableRentArray<T>(Array.Empty<T>(), shouldReturnPool: false); case ICollection<T> collection: { var rentArray = new DisposableRentArray<T>(collection.Count); collection.CopyTo(rentArray.Array, 0); return rentArray; } } // 要素数がわからない場合は最初にある程度の長さの配列を用意して、足りなくなったら新しい配列を借りてくる const int defaultCount = 32; var pool = ArrayPool<T>.Shared; var index = 0; var bufferArray = pool.Rent(defaultCount); foreach (var item in source) { if (bufferArray.Length <= index) { var newSize = bufferArray.Length * 2; var newArray = pool.Rent((index < newSize) ? newSize : (index * 2)); Array.Copy(bufferArray, 0, newArray, 0, bufferArray.Length); pool.Return(bufferArray); bufferArray = newArray; } bufferArray[index++] = item; } return new DisposableRentArray<T>(bufferArray, shouldReturnPool: true); } } /// <summary> /// ArrayPoolから借りる際のラッパー /// Disposeで返却されるので、Dispose漏れに注意 /// </summary> /// <typeparam name="T">要素の型</typeparam> public readonly ref struct DisposableRentArray<T> { public readonly T[] Array; private readonly int _length; private readonly bool _shouldReturnPool; /// <summary> /// コンストラクタ /// </summary> /// <param name="minLength">ArrayPoolから借りる配列の最小値</param> public DisposableRentArray(int minLength) { Array = ArrayPool<T>.Shared.Rent(minLength); _length = minLength; _shouldReturnPool = true; } /// <summary> /// 間違って既に配列なインスタンスを渡されたらプールに返さないよう無関係に処理する /// </summary> public DisposableRentArray(T[] array, bool shouldReturnPool = false) { Array = array; _length = array.Length; _shouldReturnPool = shouldReturnPool; } /// <summary>ArrayPoolから借りた配列を返却する</summary> public void Dispose() { if (!_shouldReturnPool) { return; } System.Array.Clear(Array, 0, _length); ArrayPool<T>.Shared.Return(Array); } }
所感
DisposableRentArrayはref structにしてるけど、そこまでしなくていいかもしれない。 でもヒープに確保する用途なら素直にToArrayすればよくない?とも思う。
ところで、Dispose可能なインスタンスは絶対Disposeしてほしいんですが、されない書き方をコンパイルエラーにする方法って無いでしょうか。(今は型の名前にDisposableって付けて、Dispose忘れに気付いてほしい気持ちを表現していますが、長いし付けたくない)
静的解析とか勉強すればいいのかもしれない。
→ Dispose漏れを警告してくれるアナライザあった。