Skip to content

Commit 62d1a2a

Browse files
committed
Added support for casting Memory<char> from string
1 parent ae2844a commit 62d1a2a

File tree

3 files changed

+137
-3
lines changed

3 files changed

+137
-3
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Buffers;
7+
using System.Runtime.CompilerServices;
8+
using System.Runtime.InteropServices;
9+
using Microsoft.Toolkit.HighPerformance.Buffers.Internals.Interfaces;
10+
using Microsoft.Toolkit.HighPerformance.Extensions;
11+
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
12+
13+
namespace Microsoft.Toolkit.HighPerformance.Buffers.Internals
14+
{
15+
/// <summary>
16+
/// A custom <see cref="MemoryManager{T}"/> that casts data from a <see cref="string"/> to <typeparamref name="TTo"/> values.
17+
/// </summary>
18+
/// <typeparam name="TTo">The target type to cast the source characters to.</typeparam>
19+
internal sealed class StringMemoryManager<TTo> : MemoryManager<TTo>, IMemoryManager
20+
where TTo : unmanaged
21+
{
22+
/// <summary>
23+
/// The source <see cref="string"/> to read data from.
24+
/// </summary>
25+
private readonly string text;
26+
27+
/// <summary>
28+
/// The starting offset within <see name="array"/>.
29+
/// </summary>
30+
private readonly int offset;
31+
32+
/// <summary>
33+
/// The original used length for <see name="array"/>.
34+
/// </summary>
35+
private readonly int length;
36+
37+
/// <summary>
38+
/// Initializes a new instance of the <see cref="StringMemoryManager{T}"/> class.
39+
/// </summary>
40+
/// <param name="text">The source <see cref="string"/> to read data from.</param>
41+
/// <param name="offset">The starting offset within <paramref name="text"/>.</param>
42+
/// <param name="length">The original used length for <paramref name="text"/>.</param>
43+
public StringMemoryManager(string text, int offset, int length)
44+
{
45+
this.text = text;
46+
this.offset = offset;
47+
this.length = length;
48+
}
49+
50+
/// <inheritdoc/>
51+
public override Span<TTo> GetSpan()
52+
{
53+
#if SPAN_RUNTIME_SUPPORT
54+
ref char r0 = ref this.text.DangerousGetReferenceAt(this.offset);
55+
ref TTo r1 = ref Unsafe.As<char, TTo>(ref r0);
56+
int length = RuntimeHelpers.ConvertLength<char, TTo>(this.length);
57+
58+
return MemoryMarshal.CreateSpan(ref r1, length);
59+
#else
60+
ReadOnlyMemory<char> memory = this.text.AsMemory(this.offset, this.length);
61+
Span<char> span = MemoryMarshal.AsMemory(memory).Span;
62+
63+
return MemoryMarshal.Cast<char, TTo>(span);
64+
#endif
65+
}
66+
67+
/// <inheritdoc/>
68+
public override unsafe MemoryHandle Pin(int elementIndex = 0)
69+
{
70+
if ((uint)elementIndex >= (uint)(this.length * Unsafe.SizeOf<char>() / Unsafe.SizeOf<TTo>()))
71+
{
72+
ThrowArgumentOutOfRangeExceptionForInvalidIndex();
73+
}
74+
75+
int
76+
bytePrefix = this.offset * Unsafe.SizeOf<char>(),
77+
byteSuffix = elementIndex * Unsafe.SizeOf<TTo>(),
78+
byteOffset = bytePrefix + byteSuffix;
79+
80+
GCHandle handle = GCHandle.Alloc(this.text, GCHandleType.Pinned);
81+
82+
ref char r0 = ref this.text.DangerousGetReference();
83+
ref byte r1 = ref Unsafe.As<char, byte>(ref r0);
84+
ref byte r2 = ref Unsafe.Add(ref r1, byteOffset);
85+
void* pi = Unsafe.AsPointer(ref r2);
86+
87+
return new MemoryHandle(pi, handle);
88+
}
89+
90+
/// <inheritdoc/>
91+
public override void Unpin()
92+
{
93+
}
94+
95+
/// <inheritdoc/>
96+
protected override void Dispose(bool disposing)
97+
{
98+
}
99+
100+
/// <inheritdoc/>
101+
public Memory<T> GetMemory<T>(int offset, int length)
102+
where T : unmanaged
103+
{
104+
int
105+
absoluteOffset = this.offset + RuntimeHelpers.ConvertLength<TTo, char>(offset),
106+
absoluteLength = RuntimeHelpers.ConvertLength<TTo, char>(length);
107+
108+
if (typeof(T) == typeof(char))
109+
{
110+
ReadOnlyMemory<char> memory = this.text.AsMemory(absoluteOffset, absoluteLength);
111+
112+
return (Memory<T>)(object)MemoryMarshal.AsMemory(memory);
113+
}
114+
115+
return new StringMemoryManager<T>(this.text, absoluteOffset, absoluteLength).Memory;
116+
}
117+
118+
/// <summary>
119+
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the target index for <see cref="Pin"/> is invalid.
120+
/// </summary>
121+
private static void ThrowArgumentOutOfRangeExceptionForInvalidIndex()
122+
{
123+
throw new ArgumentOutOfRangeException("elementIndex", "The input index is not in the valid range");
124+
}
125+
}
126+
}

Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public static class MemoryExtensions
2525
/// <exception cref="OverflowException">
2626
/// Thrown if the <see cref="Memory{T}.Length"/> property of the new <see cref="Memory{T}"/> would exceed <see cref="int.MaxValue"/>.
2727
/// </exception>
28+
/// <exception cref="ArgumentException">Thrown when the data store of <paramref name="memory"/> is not supported.</exception>
2829
[Pure]
2930
[MethodImpl(MethodImplOptions.AggressiveInlining)]
3031
public static Memory<byte> AsBytes<T>(this Memory<T> memory)
@@ -40,7 +41,7 @@ public static Memory<byte> AsBytes<T>(this Memory<T> memory)
4041
/// <typeparam name="TTo">The type of items in the destination <see cref="Memory{T}"/>.</typeparam>
4142
/// <param name="memory">The source <see cref="Memory{T}"/>, of type <typeparamref name="TFrom"/>.</param>
4243
/// <returns>A <see cref="Memory{T}"/> of type <typeparamref name="TTo"/></returns>
43-
/// <exception cref="ArgumentException">Thrown when the data store of <paramref name="memory"/> is not supported (eg. when it is a <see cref="string"/>).</exception>
44+
/// <exception cref="ArgumentException">Thrown when the data store of <paramref name="memory"/> is not supported.</exception>
4445
[Pure]
4546
[MethodImpl(MethodImplOptions.AggressiveInlining)]
4647
public static Memory<TTo> Cast<TFrom, TTo>(this Memory<TFrom> memory)

Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public static class ReadOnlyMemoryExtensions
2828
/// <exception cref="OverflowException">
2929
/// Thrown if the <see cref="ReadOnlyMemory{T}.Length"/> property of the new <see cref="ReadOnlyMemory{T}"/> would exceed <see cref="int.MaxValue"/>.
3030
/// </exception>
31+
/// <exception cref="ArgumentException">Thrown when the data store of <paramref name="memory"/> is not supported.</exception>
3132
[Pure]
3233
[MethodImpl(MethodImplOptions.AggressiveInlining)]
3334
public static ReadOnlyMemory<byte> AsBytes<T>(this ReadOnlyMemory<T> memory)
@@ -43,7 +44,7 @@ public static ReadOnlyMemory<byte> AsBytes<T>(this ReadOnlyMemory<T> memory)
4344
/// <typeparam name="TTo">The type of items in the destination <see cref="ReadOnlyMemory{T}"/>.</typeparam>
4445
/// <param name="memory">The source <see cref="ReadOnlyMemory{T}"/>, of type <typeparamref name="TFrom"/>.</param>
4546
/// <returns>A <see cref="ReadOnlyMemory{T}"/> of type <typeparamref name="TTo"/></returns>
46-
/// <exception cref="ArgumentException">Thrown when the data store of <paramref name="memory"/> is not supported (eg. when it is a <see cref="string"/>).</exception>
47+
/// <exception cref="ArgumentException">Thrown when the data store of <paramref name="memory"/> is not supported.</exception>
4748
[Pure]
4849
[MethodImpl(MethodImplOptions.AggressiveInlining)]
4950
public static ReadOnlyMemory<TTo> Cast<TFrom, TTo>(this ReadOnlyMemory<TFrom> memory)
@@ -55,12 +56,18 @@ public static ReadOnlyMemory<TTo> Cast<TFrom, TTo>(this ReadOnlyMemory<TFrom> me
5556
return default;
5657
}
5758

59+
if (typeof(TFrom) == typeof(char) &&
60+
MemoryMarshal.TryGetString((ReadOnlyMemory<char>)(object)memory, out string? text, out int start, out int length))
61+
{
62+
return new StringMemoryManager<TTo>(text!, start, length).Memory;
63+
}
64+
5865
if (MemoryMarshal.TryGetArray(memory, out ArraySegment<TFrom> segment))
5966
{
6067
return new ArrayMemoryManager<TFrom, TTo>(segment.Array!, segment.Offset, segment.Count).Memory;
6168
}
6269

63-
if (MemoryMarshal.TryGetMemoryManager<TFrom, MemoryManager<TFrom>>(memory, out var memoryManager, out int start, out int length))
70+
if (MemoryMarshal.TryGetMemoryManager<TFrom, MemoryManager<TFrom>>(memory, out var memoryManager, out start, out length))
6471
{
6572
// If the memory manager is the one resulting from a previous cast, we can use it directly to retrieve
6673
// a new manager for the target type that wraps the original data store, instead of creating one that

0 commit comments

Comments
 (0)