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.
6 using System
.Diagnostics
;
7 using System
.Runtime
.CompilerServices
;
8 using System
.Runtime
.InteropServices
;
10 using EditorBrowsableAttribute
= System
.ComponentModel
.EditorBrowsableAttribute
;
11 using EditorBrowsableState
= System
.ComponentModel
.EditorBrowsableState
;
13 using Internal
.Runtime
.CompilerServices
;
15 #pragma warning disable SA1121 // explicitly using type aliases instead of built-in types
17 using nuint
= System
.UInt64
;
19 using nuint
= System
.UInt32
;
25 /// Memory represents a contiguous region of arbitrary memory similar to <see cref="Span{T}"/>.
26 /// Unlike <see cref="Span{T}"/>, it is not a byref-like type.
28 [DebuggerTypeProxy(typeof(MemoryDebugView
<>))]
29 [DebuggerDisplay("{ToString(),raw}")]
30 public readonly struct Memory
<T
> : IEquatable
<Memory
<T
>>
32 // NOTE: With the current implementation, Memory<T> and ReadOnlyMemory<T> must have the same layout,
33 // as code uses Unsafe.As to cast between them.
35 // The highest order bit of _index is used to discern whether _object is a pre-pinned array.
36 // (_index < 0) => _object is a pre-pinned array, so Pin() will not allocate a new GCHandle
37 // (else) => Pin() needs to allocate a new GCHandle to pin the object.
38 private readonly object? _object
;
39 private readonly int _index
;
40 private readonly int _length
;
43 /// Creates a new memory over the entirety of the target array.
45 /// <param name="array">The target array.</param>
46 /// <remarks>Returns default when <paramref name="array"/> is null.</remarks>
47 /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="array"/> is covariant and array's type is not exactly T[].</exception>
48 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
49 public Memory(T
[]? array
)
54 return; // returns default
56 if (default(T
)! == null && array
.GetType() != typeof(T
[])) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
57 ThrowHelper
.ThrowArrayTypeMismatchException();
61 _length
= array
.Length
;
64 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
65 internal Memory(T
[]? array
, int start
)
70 ThrowHelper
.ThrowArgumentOutOfRangeException();
72 return; // returns default
74 if (default(T
)! == null && array
.GetType() != typeof(T
[])) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
75 ThrowHelper
.ThrowArrayTypeMismatchException();
76 if ((uint)start
> (uint)array
.Length
)
77 ThrowHelper
.ThrowArgumentOutOfRangeException();
81 _length
= array
.Length
- start
;
85 /// Creates a new memory over the portion of the target array beginning
86 /// at 'start' index and ending at 'end' index (exclusive).
88 /// <param name="array">The target array.</param>
89 /// <param name="start">The index at which to begin the memory.</param>
90 /// <param name="length">The number of items in the memory.</param>
91 /// <remarks>Returns default when <paramref name="array"/> is null.</remarks>
92 /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="array"/> is covariant and array's type is not exactly T[].</exception>
93 /// <exception cref="System.ArgumentOutOfRangeException">
94 /// Thrown when the specified <paramref name="start"/> or end index is not in the range (<0 or >Length).
96 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
97 public Memory(T
[]? array
, int start
, int length
)
101 if (start
!= 0 || length
!= 0)
102 ThrowHelper
.ThrowArgumentOutOfRangeException();
104 return; // returns default
106 if (default(T
)! == null && array
.GetType() != typeof(T
[])) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
107 ThrowHelper
.ThrowArrayTypeMismatchException();
109 // See comment in Span<T>.Slice for how this works.
110 if ((ulong)(uint)start
+ (ulong)(uint)length
> (ulong)(uint)array
.Length
)
111 ThrowHelper
.ThrowArgumentOutOfRangeException();
113 if ((uint)start
> (uint)array
.Length
|| (uint)length
> (uint)(array
.Length
- start
))
114 ThrowHelper
.ThrowArgumentOutOfRangeException();
123 /// Creates a new memory from a memory manager that provides specific method implementations beginning
124 /// at 0 index and ending at 'end' index (exclusive).
126 /// <param name="manager">The memory manager.</param>
127 /// <param name="length">The number of items in the memory.</param>
128 /// <exception cref="System.ArgumentOutOfRangeException">
129 /// Thrown when the specified <paramref name="length"/> is negative.
131 /// <remarks>For internal infrastructure only</remarks>
132 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
133 internal Memory(MemoryManager
<T
> manager
, int length
)
135 Debug
.Assert(manager
!= null);
138 ThrowHelper
.ThrowArgumentOutOfRangeException();
146 /// Creates a new memory from a memory manager that provides specific method implementations beginning
147 /// at 'start' index and ending at 'end' index (exclusive).
149 /// <param name="manager">The memory manager.</param>
150 /// <param name="start">The index at which to begin the memory.</param>
151 /// <param name="length">The number of items in the memory.</param>
152 /// <exception cref="System.ArgumentOutOfRangeException">
153 /// Thrown when the specified <paramref name="start"/> or <paramref name="length"/> is negative.
155 /// <remarks>For internal infrastructure only</remarks>
156 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
157 internal Memory(MemoryManager
<T
> manager
, int start
, int length
)
159 Debug
.Assert(manager
!= null);
161 if (length
< 0 || start
< 0)
162 ThrowHelper
.ThrowArgumentOutOfRangeException();
169 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
170 internal Memory(object? obj
, int start
, int length
)
172 // No validation performed in release builds; caller must provide any necessary validation.
174 // 'obj is T[]' below also handles things like int[] <-> uint[] being convertible
175 Debug
.Assert((obj
== null)
176 || (typeof(T
) == typeof(char) && obj
is string)
177 #if FEATURE_UTF8STRING
178 || ((typeof(T
) == typeof(byte) || typeof(T
) == typeof(Char8
)) && obj
is Utf8String
)
179 #endif // FEATURE_UTF8STRING
181 || (obj
is MemoryManager
<T
>));
189 /// Defines an implicit conversion of an array to a <see cref="Memory{T}"/>
191 public static implicit operator Memory
<T
>(T
[]? array
) => new Memory
<T
>(array
);
194 /// Defines an implicit conversion of a <see cref="ArraySegment{T}"/> to a <see cref="Memory{T}"/>
196 public static implicit operator Memory
<T
>(ArraySegment
<T
> segment
) => new Memory
<T
>(segment
.Array
, segment
.Offset
, segment
.Count
);
199 /// Defines an implicit conversion of a <see cref="Memory{T}"/> to a <see cref="ReadOnlyMemory{T}"/>
201 public static implicit operator ReadOnlyMemory
<T
>(Memory
<T
> memory
) =>
202 Unsafe
.As
<Memory
<T
>, ReadOnlyMemory
<T
>>(ref memory
);
205 /// Returns an empty <see cref="Memory{T}"/>
207 public static Memory
<T
> Empty
=> default;
210 /// The number of items in the memory.
212 public int Length
=> _length
;
215 /// Returns true if Length is 0.
217 public bool IsEmpty
=> _length
== 0;
220 /// For <see cref="Memory{Char}"/>, returns a new instance of string that represents the characters pointed to by the memory.
221 /// Otherwise, returns a <see cref="string"/> with the name of the type and the number of elements.
223 public override string ToString()
225 if (typeof(T
) == typeof(char))
227 return (_object
is string str
) ? str
.Substring(_index
, _length
) : Span
.ToString();
229 #if FEATURE_UTF8STRING
230 else if (typeof(T
) == typeof(Char8
))
232 // TODO_UTF8STRING: Call into optimized transcoding routine when it's available.
234 return Encoding
.UTF8
.GetString(new ReadOnlySpan
<byte>(ref Unsafe
.As
<T
, byte>(ref MemoryMarshal
.GetReference(span
)), span
.Length
));
236 #endif // FEATURE_UTF8STRING
237 return string.Format("System.Memory<{0}>[{1}]", typeof(T
).Name
, _length
);
241 /// Forms a slice out of the given memory, beginning at 'start'.
243 /// <param name="start">The index at which to begin this slice.</param>
244 /// <exception cref="System.ArgumentOutOfRangeException">
245 /// Thrown when the specified <paramref name="start"/> index is not in range (<0 or >Length).
247 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
248 public Memory
<T
> Slice(int start
)
250 if ((uint)start
> (uint)_length
)
252 ThrowHelper
.ThrowArgumentOutOfRangeException(ExceptionArgument
.start
);
255 // It is expected for _index + start to be negative if the memory is already pre-pinned.
256 return new Memory
<T
>(_object
, _index
+ start
, _length
- start
);
260 /// Forms a slice out of the given memory, beginning at 'start', of given length
262 /// <param name="start">The index at which to begin this slice.</param>
263 /// <param name="length">The desired length for the slice (exclusive).</param>
264 /// <exception cref="System.ArgumentOutOfRangeException">
265 /// Thrown when the specified <paramref name="start"/> or end index is not in range (<0 or >Length).
267 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
268 public Memory
<T
> Slice(int start
, int length
)
271 // See comment in Span<T>.Slice for how this works.
272 if ((ulong)(uint)start
+ (ulong)(uint)length
> (ulong)(uint)_length
)
273 ThrowHelper
.ThrowArgumentOutOfRangeException();
275 if ((uint)start
> (uint)_length
|| (uint)length
> (uint)(_length
- start
))
276 ThrowHelper
.ThrowArgumentOutOfRangeException();
279 // It is expected for _index + start to be negative if the memory is already pre-pinned.
280 return new Memory
<T
>(_object
, _index
+ start
, length
);
284 /// Returns a span from the memory.
286 public unsafe Span
<T
> Span
288 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
291 // This property getter has special support for returning a mutable Span<char> that wraps
292 // an immutable String instance. This is obviously a dangerous feature and breaks type safety.
293 // However, we need to handle the case where a ReadOnlyMemory<char> was created from a string
294 // and then cast to a Memory<T>. Such a cast can only be done with unsafe or marshaling code,
295 // in which case that's the dangerous operation performed by the dev, and we're just following
296 // suit here to make it work as best as possible.
298 ref T refToReturn
= ref Unsafe
.AsRef
<T
>(null);
299 int lengthOfUnderlyingSpan
= 0;
301 // Copy this field into a local so that it can't change out from under us mid-operation.
303 object? tmpObject
= _object
;
304 if (tmpObject
!= null)
306 if (typeof(T
) == typeof(char) && tmpObject
.GetType() == typeof(string))
308 // Special-case string since it's the most common for ROM<char>.
310 refToReturn
= ref Unsafe
.As
<char, T
>(ref Unsafe
.As
<string>(tmpObject
).GetRawStringData());
311 lengthOfUnderlyingSpan
= Unsafe
.As
<string>(tmpObject
).Length
;
313 #if FEATURE_UTF8STRING
314 else if ((typeof(T
) == typeof(byte) || typeof(T
) == typeof(Char8
)) && tmpObject
.GetType() == typeof(Utf8String
))
316 refToReturn
= ref Unsafe
.As
<byte, T
>(ref Unsafe
.As
<Utf8String
>(tmpObject
).DangerousGetMutableReference());
317 lengthOfUnderlyingSpan
= Unsafe
.As
<Utf8String
>(tmpObject
).Length
;
319 #endif // FEATURE_UTF8STRING
320 else if (RuntimeHelpers
.ObjectHasComponentSize(tmpObject
))
322 // We know the object is not null, it's not a string, and it is variable-length. The only
323 // remaining option is for it to be a T[] (or a U[] which is blittable to T[], like int[]
324 // and uint[]). Otherwise somebody used private reflection to set this field, and we're not
325 // too worried about type safety violations at this point.
327 // 'tmpObject is T[]' below also handles things like int[] <-> uint[] being convertible
328 Debug
.Assert(tmpObject
is T
[]);
330 refToReturn
= ref Unsafe
.As
<byte, T
>(ref Unsafe
.As
<T
[]>(tmpObject
).GetRawSzArrayData());
331 lengthOfUnderlyingSpan
= Unsafe
.As
<T
[]>(tmpObject
).Length
;
335 // We know the object is not null, and it's not variable-length, so it must be a MemoryManager<T>.
336 // Otherwise somebody used private reflection to set this field, and we're not too worried about
337 // type safety violations at that point. Note that it can't be a MemoryManager<U>, even if U and
338 // T are blittable (e.g., MemoryManager<int> to MemoryManager<uint>), since there exists no
339 // constructor or other public API which would allow such a conversion.
341 Debug
.Assert(tmpObject
is MemoryManager
<T
>);
342 Span
<T
> memoryManagerSpan
= Unsafe
.As
<MemoryManager
<T
>>(tmpObject
).GetSpan();
343 refToReturn
= ref MemoryMarshal
.GetReference(memoryManagerSpan
);
344 lengthOfUnderlyingSpan
= memoryManagerSpan
.Length
;
347 // If the Memory<T> or ReadOnlyMemory<T> instance is torn, this property getter has undefined behavior.
348 // We try to detect this condition and throw an exception, but it's possible that a torn struct might
349 // appear to us to be valid, and we'll return an undesired span. Such a span is always guaranteed at
350 // least to be in-bounds when compared with the original Memory<T> instance, so using the span won't
353 nuint desiredStartIndex
= (uint)_index
& (uint)ReadOnlyMemory
<T
>.RemoveFlagsBitMask
;
354 int desiredLength
= _length
;
357 // See comment in Span<T>.Slice for how this works.
358 if ((ulong)desiredStartIndex
+ (ulong)(uint)desiredLength
> (ulong)(uint)lengthOfUnderlyingSpan
)
360 ThrowHelper
.ThrowArgumentOutOfRangeException();
363 if ((uint)desiredStartIndex
> (uint)lengthOfUnderlyingSpan
|| (uint)desiredLength
> (uint)(lengthOfUnderlyingSpan
- desiredStartIndex
))
365 ThrowHelper
.ThrowArgumentOutOfRangeException();
369 refToReturn
= ref Unsafe
.Add(ref refToReturn
, (IntPtr
)(void*)desiredStartIndex
);
370 lengthOfUnderlyingSpan
= desiredLength
;
373 return new Span
<T
>(ref refToReturn
, lengthOfUnderlyingSpan
);
378 /// Copies the contents of the memory into the destination. If the source
379 /// and destination overlap, this method behaves as if the original values are in
380 /// a temporary location before the destination is overwritten.
382 /// <param name="destination">The Memory to copy items into.</param>
383 /// <exception cref="System.ArgumentException">
384 /// Thrown when the destination is shorter than the source.
387 public void CopyTo(Memory
<T
> destination
) => Span
.CopyTo(destination
.Span
);
390 /// Copies the contents of the memory into the destination. If the source
391 /// and destination overlap, this method behaves as if the original values are in
392 /// a temporary location before the destination is overwritten.
394 /// <returns>If the destination is shorter than the source, this method
395 /// return false and no data is written to the destination.</returns>
397 /// <param name="destination">The span to copy items into.</param>
398 public bool TryCopyTo(Memory
<T
> destination
) => Span
.TryCopyTo(destination
.Span
);
401 /// Creates a handle for the memory.
402 /// The GC will not move the memory until the returned <see cref="MemoryHandle"/>
403 /// is disposed, enabling taking and using the memory's address.
404 /// <exception cref="System.ArgumentException">
405 /// An instance with nonprimitive (non-blittable) members cannot be pinned.
408 public unsafe MemoryHandle
Pin()
410 // Just like the Span property getter, we have special support for a mutable Memory<char>
411 // that wraps an immutable String instance. This might happen if a caller creates an
412 // immutable ROM<char> wrapping a String, then uses Unsafe.As to create a mutable M<char>.
413 // This needs to work, however, so that code that uses a single Memory<char> field to store either
414 // a readable ReadOnlyMemory<char> or a writable Memory<char> can still be pinned and
415 // used for interop purposes.
417 // It's possible that the below logic could result in an AV if the struct
418 // is torn. This is ok since the caller is expecting to use raw pointers,
419 // and we're not required to keep this as safe as the other Span-based APIs.
421 object? tmpObject
= _object
;
422 if (tmpObject
!= null)
424 if (typeof(T
) == typeof(char) && tmpObject
is string s
)
426 GCHandle handle
= GCHandle
.Alloc(tmpObject
, GCHandleType
.Pinned
);
427 ref char stringData
= ref Unsafe
.Add(ref s
.GetRawStringData(), _index
);
428 return new MemoryHandle(Unsafe
.AsPointer(ref stringData
), handle
);
430 #if FEATURE_UTF8STRING
431 else if ((typeof(T
) == typeof(byte) || typeof(T
) == typeof(Char8
)) && tmpObject
is Utf8String utf8String
)
433 GCHandle handle
= GCHandle
.Alloc(tmpObject
, GCHandleType
.Pinned
);
434 ref byte stringData
= ref utf8String
.DangerousGetMutableReference(_index
);
435 return new MemoryHandle(Unsafe
.AsPointer(ref stringData
), handle
);
437 #endif // FEATURE_UTF8STRING
438 else if (RuntimeHelpers
.ObjectHasComponentSize(tmpObject
))
440 // 'tmpObject is T[]' below also handles things like int[] <-> uint[] being convertible
441 Debug
.Assert(tmpObject
is T
[]);
443 // Array is already pre-pinned
446 void* pointer
= Unsafe
.Add
<T
>(Unsafe
.AsPointer(ref Unsafe
.As
<T
[]>(tmpObject
).GetRawSzArrayData()), _index
& ReadOnlyMemory
<T
>.RemoveFlagsBitMask
);
447 return new MemoryHandle(pointer
);
451 GCHandle handle
= GCHandle
.Alloc(tmpObject
, GCHandleType
.Pinned
);
452 void* pointer
= Unsafe
.Add
<T
>(Unsafe
.AsPointer(ref Unsafe
.As
<T
[]>(tmpObject
).GetRawSzArrayData()), _index
);
453 return new MemoryHandle(pointer
, handle
);
458 Debug
.Assert(tmpObject
is MemoryManager
<T
>);
459 return Unsafe
.As
<MemoryManager
<T
>>(tmpObject
).Pin(_index
);
467 /// Copies the contents from the memory into a new array. This heap
468 /// allocates, so should generally be avoided, however it is sometimes
469 /// necessary to bridge the gap with APIs written in terms of arrays.
471 public T
[] ToArray() => Span
.ToArray();
474 /// Determines whether the specified object is equal to the current object.
475 /// Returns true if the object is Memory or ReadOnlyMemory and if both objects point to the same array and have the same length.
477 [EditorBrowsable(EditorBrowsableState
.Never
)]
478 public override bool Equals(object? obj
)
480 if (obj
is ReadOnlyMemory
<T
>)
482 return ((ReadOnlyMemory
<T
>)obj
).Equals(this);
484 else if (obj
is Memory
<T
> memory
)
486 return Equals(memory
);
495 /// Returns true if the memory points to the same array and has the same length. Note that
496 /// this does *not* check to see if the *contents* are equal.
498 public bool Equals(Memory
<T
> other
)
501 _object
== other
._object
&&
502 _index
== other
._index
&&
503 _length
== other
._length
;
507 /// Serves as the default hash function.
509 [EditorBrowsable(EditorBrowsableState
.Never
)]
510 public override int GetHashCode()
512 // We use RuntimeHelpers.GetHashCode instead of Object.GetHashCode because the hash
513 // code is based on object identity and referential equality, not deep equality (as common with string).
514 return (_object
!= null) ? HashCode
.Combine(RuntimeHelpers
.GetHashCode(_object
), _index
, _length
) : 0;