Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 5b07e3b

Browse files
authored
Add {RO}Span GetReference and ROMemory TryGetArray to MemoryMarshal (#25789)
1 parent b76b77d commit 5b07e3b

30 files changed

+311
-54
lines changed

src/System.Memory/ref/System.Memory.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,5 +452,10 @@ namespace System.Runtime.InteropServices
452452
public static class MemoryMarshal
453453
{
454454
public static Memory<T> AsMemory<T>(ReadOnlyMemory<T> readOnlyMemory) { throw null; }
455+
456+
public static ref T GetReference<T>(Span<T> span) { throw null; }
457+
public static ref readonly T GetReference<T>(ReadOnlySpan<T> span) { throw null; }
458+
459+
public static bool TryGetArray<T>(ReadOnlyMemory<T> readOnlyMemory, out ArraySegment<T> arraySegment) { throw null; }
455460
}
456461
}

src/System.Memory/src/System/MemoryDebugView.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System.Diagnostics;
6+
using System.Runtime.InteropServices;
67

78
namespace System
89
{
@@ -27,7 +28,7 @@ public T[] Items
2728
// https://devdiv.visualstudio.com/DevDiv/_workitems?id=286592
2829
get
2930
{
30-
if (_memory.DangerousTryGetArray(out ArraySegment<T> segment))
31+
if (MemoryMarshal.TryGetArray(_memory, out ArraySegment<T> segment))
3132
{
3233
T[] array = new T[_memory.Length];
3334
Array.Copy(segment.Array, segment.Offset, array, 0, array.Length);

src/System.Memory/src/System/ReadOnlyMemory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public readonly struct ReadOnlyMemory<T>
2929
private readonly int _index;
3030
private readonly int _length;
3131

32-
private const int RemoveOwnedFlagBitMask = 0x7FFFFFFF;
32+
internal const int RemoveOwnedFlagBitMask = 0x7FFFFFFF;
3333

3434
/// <summary>
3535
/// Creates a new memory over the entirety of the target array.

src/System.Memory/src/System/ReadOnlySpan.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ public T[] ToArray()
307307
public static ReadOnlySpan<T> Empty => default(ReadOnlySpan<T>);
308308

309309
/// <summary>
310+
/// This method is obsolete, use System.Runtime.InteropServices.MemoryMarshal.GetReference instead.
310311
/// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to the location where the 0th element
311312
/// would have been stored. Such a reference can be used for pinning but must never be dereferenced.
312313
/// </summary>

src/System.Memory/src/System/Runtime/InteropServices/MemoryMarshal.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System.Buffers;
56
using System.Runtime.CompilerServices;
67

78
namespace System.Runtime.InteropServices
@@ -23,5 +24,54 @@ public static class MemoryMarshal
2324
/// </remarks>
2425
public static Memory<T> AsMemory<T>(ReadOnlyMemory<T> readOnlyMemory) =>
2526
Unsafe.As<ReadOnlyMemory<T>, Memory<T>>(ref readOnlyMemory);
27+
28+
/// <summary>
29+
/// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to the location where the 0th element
30+
/// would have been stored. Such a reference can be used for pinning but must never be dereferenced.
31+
/// </summary>
32+
public static ref T GetReference<T>(Span<T> span)
33+
{
34+
if (span.Pinnable == null)
35+
unsafe { return ref Unsafe.AsRef<T>(span.ByteOffset.ToPointer()); }
36+
else
37+
return ref Unsafe.AddByteOffset<T>(ref span.Pinnable.Data, span.ByteOffset);
38+
}
39+
40+
/// <summary>
41+
/// Returns a reference to the 0th element of the ReadOnlySpan. If the Span is empty, returns a reference to the location where the 0th element
42+
/// would have been stored. Such a reference can be used for pinning but must never be dereferenced.
43+
/// </summary>
44+
public static ref readonly T GetReference<T>(ReadOnlySpan<T> span)
45+
{
46+
if (span.Pinnable == null)
47+
unsafe { return ref Unsafe.AsRef<T>(span.ByteOffset.ToPointer()); }
48+
else
49+
return ref Unsafe.AddByteOffset<T>(ref span.Pinnable.Data, span.ByteOffset);
50+
}
51+
52+
/// <summary>
53+
/// Get an array segment from the underlying memory.
54+
/// If unable to get the array segment, return false with a default array segment.
55+
/// </summary>
56+
public static bool TryGetArray<T>(ReadOnlyMemory<T> readOnlyMemory, out ArraySegment<T> arraySegment)
57+
{
58+
object obj = readOnlyMemory.GetObjectStartLength(out int index, out int length);
59+
if (index < 0)
60+
{
61+
if (((OwnedMemory<T>)obj).TryGetArray(out var segment))
62+
{
63+
arraySegment = new ArraySegment<T>(segment.Array, segment.Offset + (index & ReadOnlyMemory<T>.RemoveOwnedFlagBitMask), length);
64+
return true;
65+
}
66+
}
67+
else if (obj is T[] arr)
68+
{
69+
arraySegment = new ArraySegment<T>(arr, index, length);
70+
return true;
71+
}
72+
73+
arraySegment = default;
74+
return false;
75+
}
2676
}
2777
}

src/System.Memory/src/System/Span.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ public T[] ToArray()
419419
public static Span<T> Empty => default(Span<T>);
420420

421421
/// <summary>
422+
/// This method is obsolete, use System.Runtime.InteropServices.MemoryMarshal.GetReference instead.
422423
/// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to the location where the 0th element
423424
/// would have been stored. Such a reference can be used for pinning but must never be dereferenced.
424425
/// </summary>

src/System.Memory/tests/Memory/Slice.cs

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

55
using Xunit;
6-
using System.Runtime.CompilerServices;
76
using System.Buffers;
7+
using System.Runtime.CompilerServices;
8+
using System.Runtime.InteropServices;
89

910
namespace System.MemoryTests
1011
{
@@ -16,13 +17,13 @@ public static void SliceWithStart()
1617
int[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 };
1718
Memory<int> memory = new Memory<int>(a).Slice(6);
1819
Assert.Equal(4, memory.Length);
19-
Assert.True(Unsafe.AreSame(ref a[6], ref memory.Span.DangerousGetPinnableReference()));
20+
Assert.True(Unsafe.AreSame(ref a[6], ref MemoryMarshal.GetReference(memory.Span)));
2021

2122
OwnedMemory<int> owner = new CustomMemoryForTest<int>(a);
2223
Memory<int> memoryFromOwner = owner.Memory.Slice(6);
2324

2425
Assert.Equal(4, memoryFromOwner.Length);
25-
Assert.True(Unsafe.AreSame(ref a[6], ref memoryFromOwner.Span.DangerousGetPinnableReference()));
26+
Assert.True(Unsafe.AreSame(ref a[6], ref MemoryMarshal.GetReference(memoryFromOwner.Span)));
2627
}
2728

2829
[Fact]
@@ -31,13 +32,13 @@ public static void SliceWithStartPastEnd()
3132
int[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 };
3233
Memory<int> memory = new Memory<int>(a).Slice(a.Length);
3334
Assert.Equal(0, memory.Length);
34-
Assert.True(Unsafe.AreSame(ref a[a.Length - 1], ref Unsafe.Subtract(ref memory.Span.DangerousGetPinnableReference(), 1)));
35+
Assert.True(Unsafe.AreSame(ref a[a.Length - 1], ref Unsafe.Subtract(ref MemoryMarshal.GetReference(memory.Span), 1)));
3536

3637
OwnedMemory<int> owner = new CustomMemoryForTest<int>(a);
3738
Memory<int> memoryFromOwner = owner.Memory.Slice(a.Length);
3839

3940
Assert.Equal(0, memoryFromOwner.Length);
40-
Assert.True(Unsafe.AreSame(ref a[a.Length - 1], ref Unsafe.Subtract(ref memoryFromOwner.Span.DangerousGetPinnableReference(), 1)));
41+
Assert.True(Unsafe.AreSame(ref a[a.Length - 1], ref Unsafe.Subtract(ref MemoryMarshal.GetReference(memoryFromOwner.Span), 1)));
4142
}
4243

4344
[Fact]
@@ -46,13 +47,13 @@ public static void SliceWithStartAndLength()
4647
int[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 };
4748
Memory<int> memory = new Memory<int>(a).Slice(3, 5);
4849
Assert.Equal(5, memory.Length);
49-
Assert.True(Unsafe.AreSame(ref a[3], ref memory.Span.DangerousGetPinnableReference()));
50+
Assert.True(Unsafe.AreSame(ref a[3], ref MemoryMarshal.GetReference(memory.Span)));
5051

5152
OwnedMemory<int> owner = new CustomMemoryForTest<int>(a);
5253
Memory<int> memoryFromOwner = owner.Memory.Slice(3, 5);
5354

5455
Assert.Equal(5, memoryFromOwner.Length);
55-
Assert.True(Unsafe.AreSame(ref a[3], ref memoryFromOwner.Span.DangerousGetPinnableReference()));
56+
Assert.True(Unsafe.AreSame(ref a[3], ref MemoryMarshal.GetReference(memoryFromOwner.Span)));
5657
}
5758

5859
[Fact]
@@ -61,13 +62,13 @@ public static void SliceWithStartAndLengthUpToEnd()
6162
int[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 };
6263
Memory<int> memory = new Memory<int>(a).Slice(4, 6);
6364
Assert.Equal(6, memory.Length);
64-
Assert.True(Unsafe.AreSame(ref a[4], ref memory.Span.DangerousGetPinnableReference()));
65+
Assert.True(Unsafe.AreSame(ref a[4], ref MemoryMarshal.GetReference(memory.Span)));
6566

6667
OwnedMemory<int> owner = new CustomMemoryForTest<int>(a);
6768
Memory<int> memoryFromOwner = owner.Memory.Slice(4, 6);
6869

6970
Assert.Equal(6, memoryFromOwner.Length);
70-
Assert.True(Unsafe.AreSame(ref a[4], ref memoryFromOwner.Span.DangerousGetPinnableReference()));
71+
Assert.True(Unsafe.AreSame(ref a[4], ref MemoryMarshal.GetReference(memoryFromOwner.Span)));
7172
}
7273

7374
[Fact]
@@ -76,13 +77,13 @@ public static void SliceWithStartAndLengthPastEnd()
7677
int[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 };
7778
Memory<int> memory = new Memory<int>(a).Slice(a.Length, 0);
7879
Assert.Equal(0, memory.Length);
79-
Assert.True(Unsafe.AreSame(ref a[a.Length - 1], ref Unsafe.Subtract(ref memory.Span.DangerousGetPinnableReference(), 1)));
80+
Assert.True(Unsafe.AreSame(ref a[a.Length - 1], ref Unsafe.Subtract(ref MemoryMarshal.GetReference(memory.Span), 1)));
8081

8182
OwnedMemory<int> owner = new CustomMemoryForTest<int>(a);
8283
Memory<int> memoryFromOwner = owner.Memory.Slice(a.Length, 0);
8384

8485
Assert.Equal(0, memoryFromOwner.Length);
85-
Assert.True(Unsafe.AreSame(ref a[a.Length - 1], ref Unsafe.Subtract(ref memoryFromOwner.Span.DangerousGetPinnableReference(), 1)));
86+
Assert.True(Unsafe.AreSame(ref a[a.Length - 1], ref Unsafe.Subtract(ref MemoryMarshal.GetReference(memoryFromOwner.Span), 1)));
8687
}
8788

8889
[Fact]

src/System.Memory/tests/MemoryMarshal/AsMemory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ private static unsafe void AsMemory_Roundtrips_Core<T>(ReadOnlyMemory<T> readOnl
8181
Assert.True(readOnlyMemory.Span == memory.Span);
8282

8383
// TryGetArray
84-
Assert.True(readOnlyMemory.DangerousTryGetArray(out ArraySegment<T> array1) == memory.TryGetArray(out ArraySegment<T> array2));
84+
Assert.True(MemoryMarshal.TryGetArray(readOnlyMemory, out ArraySegment<T> array1) == memory.TryGetArray(out ArraySegment<T> array2));
8585
Assert.Same(array1.Array, array2.Array);
8686
Assert.Equal(array1.Offset, array2.Offset);
8787
Assert.Equal(array1.Count, array2.Count);
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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 Xunit;
6+
using System.Runtime.CompilerServices;
7+
8+
using static System.TestHelpers;
9+
using System.Runtime.InteropServices;
10+
11+
namespace System.SpanTests
12+
{
13+
public static partial class MemoryMarshalTests
14+
{
15+
[Fact]
16+
public static void SpanGetReferenceArray()
17+
{
18+
int[] a = { 91, 92, 93, 94, 95 };
19+
Span<int> span = new Span<int>(a, 1, 3);
20+
ref int pinnableReference = ref MemoryMarshal.GetReference(span);
21+
Assert.True(Unsafe.AreSame(ref a[1], ref pinnableReference));
22+
}
23+
24+
[Fact]
25+
public static void SpanGetReferenceArrayPastEnd()
26+
{
27+
// The only real difference between GetReference() and "ref span[0]" is that
28+
// GetReference() of a zero-length won't throw an IndexOutOfRange.
29+
30+
int[] a = { 91, 92, 93, 94, 95 };
31+
Span<int> span = new Span<int>(a, a.Length, 0);
32+
ref int pinnableReference = ref MemoryMarshal.GetReference(span);
33+
ref int expected = ref Unsafe.Add<int>(ref a[a.Length - 1], 1);
34+
Assert.True(Unsafe.AreSame(ref expected, ref pinnableReference));
35+
}
36+
37+
[Fact]
38+
public static void SpanGetReferencePointer()
39+
{
40+
unsafe
41+
{
42+
int i = 42;
43+
Span<int> span = new Span<int>(&i, 1);
44+
ref int pinnableReference = ref MemoryMarshal.GetReference(span);
45+
Assert.True(Unsafe.AreSame(ref i, ref pinnableReference));
46+
}
47+
}
48+
49+
[Fact]
50+
public static void SpanGetReferencePointerDangerousCreate1()
51+
{
52+
TestClass testClass = new TestClass();
53+
Span<char> span = Span<char>.DangerousCreate(testClass, ref testClass.C1, 3);
54+
55+
ref char pinnableReference = ref MemoryMarshal.GetReference(span);
56+
Assert.True(Unsafe.AreSame(ref testClass.C1, ref pinnableReference));
57+
}
58+
59+
[Fact]
60+
public static void SpanGetReferenceEmpty()
61+
{
62+
unsafe
63+
{
64+
Span<int> span = Span<int>.Empty;
65+
ref int pinnableReference = ref MemoryMarshal.GetReference(span);
66+
Assert.True(Unsafe.AreSame(ref Unsafe.AsRef<int>(null), ref pinnableReference));
67+
}
68+
}
69+
70+
[Fact]
71+
public static void ReadOnlySpanGetReferenceArray()
72+
{
73+
int[] a = { 91, 92, 93, 94, 95 };
74+
ReadOnlySpan<int> span = new ReadOnlySpan<int>(a, 1, 3);
75+
ref int pinnableReference = ref Unsafe.AsRef(in MemoryMarshal.GetReference(span));
76+
Assert.True(Unsafe.AreSame(ref a[1], ref pinnableReference));
77+
}
78+
79+
[Fact]
80+
public static void ReadOnlySpanGetReferenceArrayPastEnd()
81+
{
82+
// The only real difference between GetReference() and "ref span[0]" is that
83+
// GetReference() of a zero-length won't throw an IndexOutOfRange.
84+
85+
int[] a = { 91, 92, 93, 94, 95 };
86+
ReadOnlySpan<int> span = new ReadOnlySpan<int>(a, a.Length, 0);
87+
ref int pinnableReference = ref Unsafe.AsRef(in MemoryMarshal.GetReference(span));
88+
ref int expected = ref Unsafe.Add<int>(ref a[a.Length - 1], 1);
89+
Assert.True(Unsafe.AreSame(ref expected, ref pinnableReference));
90+
}
91+
92+
[Fact]
93+
public static void ReadOnlySpanGetReferencePointer()
94+
{
95+
unsafe
96+
{
97+
int i = 42;
98+
ReadOnlySpan<int> span = new ReadOnlySpan<int>(&i, 1);
99+
ref int pinnableReference = ref Unsafe.AsRef(in MemoryMarshal.GetReference(span));
100+
Assert.True(Unsafe.AreSame(ref i, ref pinnableReference));
101+
}
102+
}
103+
104+
[Fact]
105+
public static void ReadOnlySpanGetReferencePointerDangerousCreate1()
106+
{
107+
TestClass testClass = new TestClass();
108+
ReadOnlySpan<char> span = ReadOnlySpan<char>.DangerousCreate(testClass, ref testClass.C1, 3);
109+
110+
ref char pinnableReference = ref Unsafe.AsRef(in MemoryMarshal.GetReference(span));
111+
Assert.True(Unsafe.AreSame(ref testClass.C1, ref pinnableReference));
112+
}
113+
114+
[Fact]
115+
public static void ReadOnlySpanGetReferenceEmpty()
116+
{
117+
unsafe
118+
{
119+
ReadOnlySpan<int> span = ReadOnlySpan<int>.Empty;
120+
ref int pinnableReference = ref Unsafe.AsRef(in MemoryMarshal.GetReference(span));
121+
Assert.True(Unsafe.AreSame(ref Unsafe.AsRef<int>(null), ref pinnableReference));
122+
}
123+
}
124+
}
125+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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.Buffers;
6+
using System.Runtime.InteropServices;
7+
using Xunit;
8+
9+
namespace System.MemoryTests
10+
{
11+
public static partial class MemoryMarshalTests
12+
{
13+
[Fact]
14+
public static void ReadOnlyMemoryTryGetArray()
15+
{
16+
int[] array = new int[10];
17+
ReadOnlyMemory<int> memory = array;
18+
Assert.True(MemoryMarshal.TryGetArray(memory, out ArraySegment<int> segment));
19+
Assert.Equal(array.Length, segment.Count);
20+
21+
for (int i = segment.Offset; i < segment.Count + segment.Offset; i++)
22+
{
23+
Assert.Equal(array[i], segment.Array[i]);
24+
}
25+
}
26+
27+
[Fact]
28+
public static void TryGetArrayFromDefaultMemory()
29+
{
30+
ReadOnlyMemory<int> memory = default;
31+
Assert.False(MemoryMarshal.TryGetArray(memory, out ArraySegment<int> segment));
32+
Assert.True(segment.Equals(default));
33+
}
34+
35+
[Fact]
36+
public static void OwnedMemoryTryGetArray()
37+
{
38+
int[] array = new int[10];
39+
OwnedMemory<int> owner = new CustomMemoryForTest<int>(array);
40+
ReadOnlyMemory<int> memory = owner.Memory;
41+
Assert.True(MemoryMarshal.TryGetArray(memory, out ArraySegment<int> segment));
42+
Assert.Equal(array.Length, segment.Count);
43+
44+
for (int i = segment.Offset; i < segment.Count + segment.Offset; i++)
45+
{
46+
Assert.Equal(array[i], segment.Array[i]);
47+
}
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)