Skip to content

Commit

Permalink
Add ArrayPoolRenter<T>
Browse files Browse the repository at this point in the history
Add ReadOnlySpan<T>.CopyToAndSliceDest()
  • Loading branch information
aianlinb committed May 17, 2024
1 parent 231217b commit b20bc34
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 8 deletions.
45 changes: 40 additions & 5 deletions SystemExtensions.Test/SpanExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ public void CopyToAndSlice_Test(int length) {
Random.Shared.NextBytes(input);
var span1 = new ReadOnlySpan<byte>(input);
var span2 = new Span<byte>(output);
var span1Snapshot = span1;
var span2Snapshot = span2;
var spanshot1 = span1;
var spanshot2 = span2;

// Act
try {
Expand All @@ -73,9 +73,44 @@ public void CopyToAndSlice_Test(int length) {
Assert.IsTrue(length >= 0 && length <= input.Length);

// Assert
Assert.IsTrue(span1Snapshot[..length].SequenceEqual(span2Snapshot[..length]));
Assert.IsTrue(span1 == span1Snapshot[length..]);
Assert.IsTrue(span2 == span2Snapshot[length..]);
Assert.IsTrue(spanshot1[..length].SequenceEqual(spanshot2[..length]));
Assert.IsTrue(span1 == spanshot1[length..]);
Assert.IsTrue(span2 == spanshot2[length..]);
}
[TestMethod]
public void CopyToAndSliceDest_Test() {
// Arrange
var input = new byte[5];
var output = new byte[10];
Random.Shared.NextBytes(input);
var span1 = new ReadOnlySpan<byte>(input);
var span2 = new Span<byte>(output);
var snapshot1 = span1;
var snapshot2 = span2;

// Act
span1.CopyToAndSliceDest(ref span2);

// Assert
Assert.IsTrue(span1.SequenceEqual(snapshot2[..input.Length]));
Assert.IsTrue(snapshot1 == span1);
Assert.IsTrue(span2 == snapshot2[input.Length..]);
}

[TestMethod]
public void AsSpan_Test() {
// Arrange
// Any unmanaged type
var struct1 = DateTime.UtcNow;
var struct2 = Guid.NewGuid();

// Act
var actual1 = struct1.AsSpan();
var actual2 = struct2.AsSpan();

// Assert
Assert.IsTrue(MemoryMarshal.AsBytes<DateTime>(new(ref struct1)) == actual1);
Assert.IsTrue(MemoryMarshal.AsBytes<Guid>(new(ref struct2)) == actual2);
}

[TestMethod]
Expand Down
66 changes: 66 additions & 0 deletions SystemExtensions/Collections/ArrayPoolRenter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System.Buffers;

namespace SystemExtensions.Collections;

/// <summary>
/// Rents an array from <see cref="ArrayPool{T}.Shared"/> and returns it when disposed.
/// </summary>
public sealed class ArrayPoolRenter<T>(int minimumLength) : IDisposable { // Must be a class to prevent duplicate
/// <summary>
/// The rented array. Will be <see langword="null"/> after <see cref="Return"/> or <see cref="Dispose"/>.
/// </summary>
public T[] Array { get; private set; } = ArrayPool<T>.Shared.Rent(minimumLength);

/// <summary>
/// Rents a array with <paramref name="minimumLength"/> and returns the old one.
/// </summary>
/// <remarks>
/// The new array will be stored in <see cref="Array"/>.
/// </remarks>
public void Resize(int minimumLength) {
lock (this) {
var newArray = ArrayPool<T>.Shared.Rent(minimumLength); // Rent first to check the argument
Return();
Array = newArray;
}
}

/// <summary>
/// Rents a array with <paramref name="minimumLength"/>
/// and copies the first <paramref name="copyLength"/> elements to the new array.
/// And then returns the old one.
/// </summary>
/// <remarks>
/// The new array will be stored in <see cref="Array"/>.
/// </remarks>
public void Resize(int minimumLength, int copyLength) {
lock (this) {
var newArray = ArrayPool<T>.Shared.Rent(minimumLength);
try {
new ReadOnlySpan<T>(Array, 0, copyLength).CopyTo(newArray);
} catch {
ArrayPool<T>.Shared.Return(newArray);
throw;
}
Return();
Array = newArray;
}
}

/// <summary>
/// Returns the <see cref="Array"/> to the pool, and sets it to <see langword="null"/>.
/// </summary>
public void Return(bool clearArray = false) {
lock (this) {
if (Array is not null) {
ArrayPool<T>.Shared.Return(Array, clearArray);
Array = null!;
}
}
}

/// <summary>
/// Calls <see cref="Return"/>.
/// </summary>
public void Dispose() => Return();
}
28 changes: 26 additions & 2 deletions SystemExtensions/Spans/SpanExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,48 @@ public static class SpanExtensions {
public static ReadOnlySpan<T> AsReadOnlySpan<T>(this T[] array) => array;
public static ReadOnlyMemory<T> AsReadOnlyMemory<T>(this T[] array) => array;

/// <summary>
/// Reads a value of type <typeparamref name="T"/> from the <paramref name="source"/>
/// and advances the <paramref name="source"/> by <see langword="sizeof"/>(<typeparamref name="T"/>).
/// </summary>
public static unsafe T ReadAndSlice<T>(this scoped ref ReadOnlySpan<byte> source) where T : unmanaged {
ref var p = ref MemoryMarshal.GetReference(source);
source = source.Slice(sizeof(T)); // range check here
return Unsafe.As<byte, T>(ref p);
}
/// <summary>
/// Writes a <paramref name="value"/> to the <paramref name="source"/>
/// and advances the <paramref name="source"/> by <see langword="sizeof"/>(<typeparamref name="T"/>).
/// </summary>
public static unsafe void WriteAndSlice<T>(this scoped ref Span<byte> source, T value) where T : unmanaged {
ref var p = ref MemoryMarshal.GetReference(source);
ref var p2 = ref MemoryMarshal.GetReference(source);
source = source.Slice(sizeof(T)); // range check here
Unsafe.As<byte, T>(ref p) = value;
Unsafe.As<byte, T>(ref p2) = value;
}
/// <summary>
/// Copies <paramref name="length"/> bytes from the <paramref name="source"/> to the <paramref name="destination"/>
/// and advances both two spans by <paramref name="length"/>.
/// </summary>
public static unsafe void CopyToAndSlice<T>(this scoped ref ReadOnlySpan<T> source, scoped ref Span<T> destination, int length) where T : unmanaged {
ref var p = ref MemoryMarshal.GetReference(source);
ref var p2 = ref MemoryMarshal.GetReference(destination);
source = source.Slice(length); // range check here
destination = destination.Slice(length); // and here
MemoryMarshal.CreateReadOnlySpan(ref p, length).CopyToUnchecked(ref p2);
}
/// <summary>
/// Copies <paramref name="source"/> to the <paramref name="destination"/>
/// and advances the <paramref name="destination"/> by <paramref name="source"/>.Length.
/// </summary>
public static unsafe void CopyToAndSliceDest<T>(this scoped ReadOnlySpan<T> source, scoped ref Span<T> destination) where T : unmanaged {
ref var p2 = ref MemoryMarshal.GetReference(destination);
destination = destination.Slice(source.Length); // range check here
source.CopyToUnchecked(ref p2);
}

/// <summary>
/// Returns a <see cref="Span{T}"/> that represents the memory of the by reference parameter <paramref name="value"/>.
/// </summary>
public static unsafe Span<byte> AsSpan<T>(this ref T value) where T : unmanaged
=> MemoryMarshal.CreateSpan(ref Unsafe.As<T, byte>(ref value), sizeof(T));

Expand Down
2 changes: 1 addition & 1 deletion SystemExtensions/SystemExtensions.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</PropertyGroup>

<PropertyGroup Label="PackageInfo">
<Version>1.5.1</Version>
<Version>1.5.2</Version>
<Authors>aianlinb</Authors>
<Copyright>Copyright © 2024 aianlinb</Copyright>
<Description>Library with useful high-performance methods for Span/Stream/List etc...</Description>
Expand Down

0 comments on commit b20bc34

Please sign in to comment.