diff --git a/CommunityToolkit.HighPerformance/Extensions/ArrayPoolExtensions.cs b/CommunityToolkit.HighPerformance/Extensions/ArrayPoolExtensions.cs index 6b262ce8..e59a5ea7 100644 --- a/CommunityToolkit.HighPerformance/Extensions/ArrayPoolExtensions.cs +++ b/CommunityToolkit.HighPerformance/Extensions/ArrayPoolExtensions.cs @@ -53,4 +53,45 @@ public static void Resize(this ArrayPool pool, [NotNull] ref T[]? array, i array = newArray; } + + /// + /// Ensures that when the method returns is not null and is at least in length. + /// Contents of are not copied if a new array is rented. + /// + /// The type of items into the target array given as input. + /// The target instance used to rent and/or return the array. + /// The rented array to ensure capacity for, or to rent a new array. + /// The minimum length of when the method returns. + /// Indicates whether the contents of the array should be cleared if returned to the pool. + /// Thrown when is less than 0. + /// When this method returns, the caller must not use any references to the old array anymore. + public static void EnsureCapacity(this ArrayPool pool, [NotNull] ref T[]? array, int capacity, bool clearArray = false) + { + if (capacity < 0) + { + ThrowArgumentOutOfRangeExceptionForNegativeArrayCapacity(); + } + + if (array is null) + { + array = pool.Rent(capacity); + } + else if (array.Length < capacity) + { + // Ensure rent succeeds before returning the original array to the pool + T[] newArray = pool.Rent(capacity); + + pool.Return(array, clearArray); + + array = newArray; + } + } + + /// + /// Throws an when the "capacity" parameter is negative. + /// + private static void ThrowArgumentOutOfRangeExceptionForNegativeArrayCapacity() + { + throw new ArgumentOutOfRangeException("capacity", "The array capacity must be a positive number."); + } } diff --git a/tests/CommunityToolkit.HighPerformance.UnitTests/Extensions/Test_ArrayPoolExtensions.cs b/tests/CommunityToolkit.HighPerformance.UnitTests/Extensions/Test_ArrayPoolExtensions.cs index 5ab21e6a..a215c082 100644 --- a/tests/CommunityToolkit.HighPerformance.UnitTests/Extensions/Test_ArrayPoolExtensions.cs +++ b/tests/CommunityToolkit.HighPerformance.UnitTests/Extensions/Test_ArrayPoolExtensions.cs @@ -15,7 +15,7 @@ public class Test_ArrayPoolExtensions { [TestMethod] [ExpectedException(typeof(ArgumentOutOfRangeException))] - public void Test_ArrayExtensions_InvalidSize() + public void Test_ArrayPoolExtensions_Resize_InvalidSize() { int[]? array = null; @@ -23,7 +23,7 @@ public void Test_ArrayExtensions_InvalidSize() } [TestMethod] - public void Test_ArrayExtensions_NewArray() + public void Test_ArrayPoolExtensions_Resize_NewArray() { int[]? array = null; @@ -34,7 +34,7 @@ public void Test_ArrayExtensions_NewArray() } [TestMethod] - public void Test_ArrayExtensions_SameSize() + public void Test_ArrayPoolExtensions_Resize_SameSize() { int[] array = ArrayPool.Shared.Rent(10); int[] backup = array; @@ -45,7 +45,7 @@ public void Test_ArrayExtensions_SameSize() } [TestMethod] - public void Test_ArrayExtensions_Expand() + public void Test_ArrayPoolExtensions_Resize_Expand() { int[] array = ArrayPool.Shared.Rent(16); int[] backup = array; @@ -60,7 +60,7 @@ public void Test_ArrayExtensions_Expand() } [TestMethod] - public void Test_ArrayExtensions_Shrink() + public void Test_ArrayPoolExtensions_Resize_Shrink() { int[] array = ArrayPool.Shared.Rent(32); int[] backup = array; @@ -75,7 +75,7 @@ public void Test_ArrayExtensions_Shrink() } [TestMethod] - public void Test_ArrayExtensions_Clear() + public void Test_ArrayPoolExtensions_Resize_Clear() { int[] array = ArrayPool.Shared.Rent(16); int[] backup = array; @@ -87,4 +87,73 @@ public void Test_ArrayExtensions_Clear() Assert.AreNotSame(array, backup); Assert.IsTrue(backup.AsSpan(0, 16).ToArray().All(i => i == 0)); } + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void Test_ArrayPoolExtensions_EnsureCapacity_InvalidCapacity() + { + int[]? array = null; + + ArrayPool.Shared.EnsureCapacity(ref array, -1); + } + + [TestMethod] + public void Test_ArrayPoolExtensions_EnsureCapacity_IdenticalCapacity() + { + int[]? array = ArrayPool.Shared.Rent(10); + int[]? backup = array; + + ArrayPool.Shared.EnsureCapacity(ref array, 10); + Assert.AreSame(backup, array); + Assert.IsTrue(array.Length >= 10); + } + + [TestMethod] + public void Test_ArrayPoolExtensions_EnsureCapacity_NewArray() + { + int[]? array = null; + + ArrayPool.Shared.EnsureCapacity(ref array, 7); + + Assert.IsNotNull(array); + Assert.IsTrue(array.Length >= 7); + int[]? backup = array; + + ArrayPool.Shared.EnsureCapacity(ref array, 64); + + Assert.AreNotSame(backup, array); + Assert.IsTrue(array.Length >= 64); + } + + [TestMethod] + public void Test_ArrayPoolExtensions_EnsureCapacity_SufficientCapacity() + { + int[]? array = ArrayPool.Shared.Rent(16); + int[]? backup = array; + + ArrayPool.Shared.EnsureCapacity(ref array, 8); + Assert.AreSame(backup, array); + + ArrayPool.Shared.EnsureCapacity(ref array, 16); + Assert.AreSame(backup, array); + + ArrayPool.Shared.EnsureCapacity(ref array, 0); + Assert.AreSame(backup, array); + } + + [TestMethod] + public void Test_ArrayPoolExtensions_EnsureCapacity_ClearArray() + { + int[]? array = ArrayPool.Shared.Rent(16); + int[]? backup = array; + + array.AsSpan().Fill(7); + Assert.IsTrue(backup.All(i => i == 7)); + + ArrayPool.Shared.EnsureCapacity(ref array, 256, true); + + Assert.AreNotSame(backup, array); + Assert.IsTrue(backup.All(i => i == default)); + Assert.IsTrue(array.Length >= 256); + } }