33// See the LICENSE file in the project root for more information.
44
55using System ;
6+ using System . Buffers ;
67using System . Diagnostics . CodeAnalysis ;
78using System . Diagnostics . Contracts ;
9+ using System . Runtime . CompilerServices ;
810using System . Runtime . InteropServices ;
911using Microsoft . Toolkit . HighPerformance . Extensions ;
1012using Microsoft . VisualStudio . TestTools . UnitTesting ;
1113
14+ #nullable enable
15+
1216namespace UnitTests . HighPerformance . Extensions
1317{
1418 public partial class Test_ReadOnlySpanExtensions
@@ -168,15 +172,15 @@ public void Test_ReadOnlySpanExtensions_FilledCount64()
168172 /// <typeparam name="T">The type to test.</typeparam>
169173 /// <param name="value">The target value to look for.</param>
170174 /// <param name="provider">The function to use to create random data.</param>
171- private static void TestForType < T > ( T value , Func < int , T , T [ ] > provider )
175+ private static void TestForType < T > ( T value , Func < int , T , UnmanagedSpanOwner < T > > provider )
172176 where T : unmanaged, IEquatable < T >
173177 {
174178 foreach ( var count in TestCounts )
175179 {
176- T [ ] data = provider ( count , value ) ;
180+ using UnmanagedSpanOwner < T > data = provider ( count , value ) ;
177181
178- int result = data . Count ( value ) ;
179- int expected = CountWithForeach ( data , value ) ;
182+ int result = data . Span . Count ( value ) ;
183+ int expected = CountWithForeach ( data . Span , value ) ;
180184
181185 Assert . AreEqual ( result , expected , $ "Failed { typeof ( T ) } test with count { count } : got { result } instead of { expected } ") ;
182186 }
@@ -214,24 +218,26 @@ private static int CountWithForeach<T>(ReadOnlySpan<T> span, T value)
214218 /// <param name="value">The value to look for.</param>
215219 /// <returns>An array of random <typeparamref name="T"/> elements.</returns>
216220 [ Pure ]
217- private static T [ ] CreateRandomData < T > ( int count , T value )
221+ private static UnmanagedSpanOwner < T > CreateRandomData < T > ( int count , T value )
218222 where T : unmanaged
219223 {
220224 var random = new Random ( count ) ;
221225
222- T [ ] data = new T [ count ] ;
226+ UnmanagedSpanOwner < T > data = new UnmanagedSpanOwner < T > ( count ) ;
223227
224- foreach ( ref byte n in MemoryMarshal . AsBytes ( data . AsSpan ( ) ) )
228+ foreach ( ref byte n in MemoryMarshal . AsBytes ( data . Span ) )
225229 {
226230 n = ( byte ) random . Next ( 0 , byte . MaxValue ) ;
227231 }
228232
229233 // Fill at least 20% of the items with a matching value
230234 int minimum = count / 20 ;
231235
236+ Span < T > span = data . Span ;
237+
232238 for ( int i = 0 ; i < minimum ; i ++ )
233239 {
234- data [ random . Next ( 0 , count ) ] = value ;
240+ span [ random . Next ( 0 , count ) ] = value ;
235241 }
236242
237243 return data ;
@@ -245,14 +251,62 @@ private static T[] CreateRandomData<T>(int count, T value)
245251 /// <param name="value">The value to use to populate the array.</param>
246252 /// <returns>An array of <typeparamref name="T"/> elements.</returns>
247253 [ Pure ]
248- private static T [ ] CreateFilledData < T > ( int count , T value )
254+ private static UnmanagedSpanOwner < T > CreateFilledData < T > ( int count , T value )
249255 where T : unmanaged
250256 {
251- T [ ] data = new T [ count ] ;
257+ UnmanagedSpanOwner < T > data = new UnmanagedSpanOwner < T > ( count ) ;
252258
253- data . AsSpan ( ) . Fill ( value ) ;
259+ data . Span . Fill ( value ) ;
254260
255261 return data ;
256262 }
263+
264+ /// <summary>
265+ /// An owner for a buffer of an unmanaged type, recycling <see cref="byte"/> arrays to save memory.
266+ /// </summary>
267+ /// <typeparam name="T">The type of items to store in the rented buffers.</typeparam>
268+ private sealed class UnmanagedSpanOwner < T > : IDisposable
269+ where T : unmanaged
270+ {
271+ /// <summary>
272+ /// The size of the current instance
273+ /// </summary>
274+ private readonly int length ;
275+
276+ /// <summary>
277+ /// The underlying <see cref="byte"/> array.
278+ /// </summary>
279+ private byte [ ] ? array ;
280+
281+ /// <summary>
282+ /// Initializes a new instance of the <see cref="UnmanagedSpanOwner{T}"/> class.
283+ /// </summary>
284+ /// <param name="size">The size of the buffer to rent.</param>
285+ public UnmanagedSpanOwner ( int size )
286+ {
287+ this . array = ArrayPool < byte > . Shared . Rent ( size * Unsafe . SizeOf < T > ( ) ) ;
288+ this . length = size ;
289+ }
290+
291+ /// <inheritdoc/>
292+ public void Dispose ( )
293+ {
294+ byte [ ] ? array = this . array ;
295+
296+ if ( array is null )
297+ {
298+ return ;
299+ }
300+
301+ this . array = null ;
302+
303+ ArrayPool < byte > . Shared . Return ( array ) ;
304+ }
305+
306+ /// <summary>
307+ /// Gets the <see cref="Memory{T}"/> for the current instance.
308+ /// </summary>
309+ public Span < T > Span => this . array . AsSpan ( ) . Cast < byte , T > ( ) . Slice ( 0 , this . length ) ;
310+ }
257311 }
258312}
0 commit comments