Skip to content

Commit 93a57d3

Browse files
authored
Merge pull request #616 from CommunityToolkit/dev/array-writer-segment
Add ArrayPoolBufferWriter<T>.DangerousGetArray() API
2 parents 0a2e8ed + 324235d commit 93a57d3

File tree

2 files changed

+81
-29
lines changed

2 files changed

+81
-29
lines changed

src/CommunityToolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs

Lines changed: 53 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,59 @@ public Span<T> GetSpan(int sizeHint = 0)
244244
return this.array.AsSpan(this.index);
245245
}
246246

247+
/// <summary>
248+
/// Gets an <see cref="ArraySegment{T}"/> instance wrapping the underlying <typeparamref name="T"/> array in use.
249+
/// </summary>
250+
/// <returns>An <see cref="ArraySegment{T}"/> instance wrapping the underlying <typeparamref name="T"/> array in use.</returns>
251+
/// <exception cref="ObjectDisposedException">Thrown when the buffer in use has already been disposed.</exception>
252+
/// <remarks>
253+
/// This method is meant to be used when working with APIs that only accept an array as input, and should be used with caution.
254+
/// In particular, the returned array is rented from an array pool, and it is responsibility of the caller to ensure that it's
255+
/// not used after the current <see cref="ArrayPoolBufferWriter{T}"/> instance is disposed. Doing so is considered undefined
256+
/// behavior, as the same array might be in use within another <see cref="ArrayPoolBufferWriter{T}"/> instance.
257+
/// </remarks>
258+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
259+
public ArraySegment<T> DangerousGetArray()
260+
{
261+
T[]? array = this.array;
262+
263+
if (array is null)
264+
{
265+
ThrowObjectDisposedException();
266+
}
267+
268+
return new(array!, 0, this.index);
269+
}
270+
271+
/// <inheritdoc/>
272+
public void Dispose()
273+
{
274+
T[]? array = this.array;
275+
276+
if (array is null)
277+
{
278+
return;
279+
}
280+
281+
this.array = null;
282+
283+
this.pool.Return(array);
284+
}
285+
286+
/// <inheritdoc/>
287+
public override string ToString()
288+
{
289+
// See comments in MemoryOwner<T> about this
290+
if (typeof(T) == typeof(char) &&
291+
this.array is char[] chars)
292+
{
293+
return new(chars, 0, this.index);
294+
}
295+
296+
// Same representation used in Span<T>
297+
return $"CommunityToolkit.HighPerformance.Buffers.ArrayPoolBufferWriter<{typeof(T)}>[{this.index}]";
298+
}
299+
247300
/// <summary>
248301
/// Ensures that <see cref="array"/> has enough free space to contain a given number of new items.
249302
/// </summary>
@@ -296,35 +349,6 @@ private void ResizeBuffer(int sizeHint)
296349
this.pool.Resize(ref this.array, (int)minimumSize);
297350
}
298351

299-
/// <inheritdoc/>
300-
public void Dispose()
301-
{
302-
T[]? array = this.array;
303-
304-
if (array is null)
305-
{
306-
return;
307-
}
308-
309-
this.array = null;
310-
311-
this.pool.Return(array);
312-
}
313-
314-
/// <inheritdoc/>
315-
public override string ToString()
316-
{
317-
// See comments in MemoryOwner<T> about this
318-
if (typeof(T) == typeof(char) &&
319-
this.array is char[] chars)
320-
{
321-
return new(chars, 0, this.index);
322-
}
323-
324-
// Same representation used in Span<T>
325-
return $"CommunityToolkit.HighPerformance.Buffers.ArrayPoolBufferWriter<{typeof(T)}>[{this.index}]";
326-
}
327-
328352
/// <summary>
329353
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the requested count is negative.
330354
/// </summary>

tests/CommunityToolkit.HighPerformance.UnitTests/Buffers/Test_ArrayPoolBufferWriter{T}.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6+
using System.Buffers;
67
using System.IO;
78
using System.Linq;
89
using System.Reflection;
10+
using System.Runtime.InteropServices;
911
using CommunityToolkit.HighPerformance;
1012
using CommunityToolkit.HighPerformance.Buffers;
1113
using CommunityToolkit.HighPerformance.UnitTests.Buffers.Internals;
@@ -218,4 +220,30 @@ public void Test_ArrayPoolBufferWriterOfT_AsStream()
218220
// Now check that the writer is actually disposed instead
219221
_ = Assert.ThrowsException<ObjectDisposedException>(() => writer.Capacity);
220222
}
223+
224+
[TestMethod]
225+
public void Test_ArrayPoolBufferWriterOfT_AllocateAndGetArray()
226+
{
227+
ArrayPoolBufferWriter<int>? bufferWriter = new();
228+
229+
// Write some random data
230+
bufferWriter.Write(Enumerable.Range(0, 127).ToArray());
231+
232+
// Get the array for the written segment
233+
ArraySegment<int> segment = bufferWriter.DangerousGetArray();
234+
235+
Assert.IsNotNull(segment.Array);
236+
Assert.IsTrue(segment.Array.Length >= bufferWriter.WrittenSpan.Length);
237+
Assert.AreEqual(segment.Offset, 0);
238+
Assert.AreEqual(segment.Count, bufferWriter.WrittenSpan.Length);
239+
240+
_ = MemoryMarshal.TryGetArray(bufferWriter.WrittenMemory, out ArraySegment<int> writtenSegment);
241+
242+
// The array is the same one as the one from the written span
243+
Assert.AreSame(segment.Array, writtenSegment.Array);
244+
245+
bufferWriter.Dispose();
246+
247+
_ = Assert.ThrowsException<ObjectDisposedException>(() => bufferWriter.DangerousGetArray());
248+
}
221249
}

0 commit comments

Comments
 (0)