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 nint
= System
.Int64
;
18 using nuint
= System
.UInt64
;
20 using nint
= System
.Int32
;
21 using nuint
= System
.UInt32
;
27 /// Memory represents a contiguous region of arbitrary memory similar to <see cref="Span{T}"/>.
28 /// Unlike <see cref="Span{T}"/>, it is not a byref-like type.
30 [DebuggerTypeProxy(typeof(MemoryDebugView
<>))]
31 [DebuggerDisplay("{ToString(),raw}")]
32 public readonly struct Memory
<T
> : IEquatable
<Memory
<T
>>
34 // NOTE: With the current implementation, Memory<T> and ReadOnlyMemory<T> must have the same layout,
35 // as code uses Unsafe.As to cast between them.
37 // The highest order bit of _index is used to discern whether _object is a pre-pinned array.
38 // (_index < 0) => _object is a pre-pinned array, so Pin() will not allocate a new GCHandle
39 // (else) => Pin() needs to allocate a new GCHandle to pin the object.
40 private readonly object? _object
;
41 private readonly int _index
;
42 private readonly int _length
;
45 /// Creates a new memory over the entirety of the target array.
47 /// <param name="array">The target array.</param>
48 /// <remarks>Returns default when <paramref name="array"/> is null.</remarks>
49 /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="array"/> is covariant and array's type is not exactly T[].</exception>
50 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
51 public Memory(T
[]? array
)
56 return; // returns default
58 if (default(T
)! == null && array
.GetType() != typeof(T
[])) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
59 ThrowHelper
.ThrowArrayTypeMismatchException();
63 _length
= array
.Length
;
66 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
67 internal Memory(T
[]? array
, int start
)
72 ThrowHelper
.ThrowArgumentOutOfRangeException();
74 return; // returns default
76 if (default(T
)! == null && array
.GetType() != typeof(T
[])) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
77 ThrowHelper
.ThrowArrayTypeMismatchException();
78 if ((uint)start
> (uint)array
.Length
)
79 ThrowHelper
.ThrowArgumentOutOfRangeException();
83 _length
= array
.Length
- start
;
87 /// Creates a new memory over the portion of the target array beginning
88 /// at 'start' index and ending at 'end' index (exclusive).
90 /// <param name="array">The target array.</param>
91 /// <param name="start">The index at which to begin the memory.</param>
92 /// <param name="length">The number of items in the memory.</param>
93 /// <remarks>Returns default when <paramref name="array"/> is null.</remarks>
94 /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="array"/> is covariant and array's type is not exactly T[].</exception>
95 /// <exception cref="System.ArgumentOutOfRangeException">
96 /// Thrown when the specified <paramref name="start"/> or end index is not in the range (<0 or >Length).
98 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
99 public Memory(T
[]? array
, int start
, int length
)
103 if (start
!= 0 || length
!= 0)
104 ThrowHelper
.ThrowArgumentOutOfRangeException();
106 return; // returns default
108 if (default(T
)! == null && array
.GetType() != typeof(T
[])) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
109 ThrowHelper
.ThrowArrayTypeMismatchException();
111 // See comment in Span<T>.Slice for how this works.
112 if ((ulong)(uint)start
+ (ulong)(uint)length
> (ulong)(uint)array
.Length
)
113 ThrowHelper
.ThrowArgumentOutOfRangeException();
115 if ((uint)start
> (uint)array
.Length
|| (uint)length
> (uint)(array
.Length
- start
))
116 ThrowHelper
.ThrowArgumentOutOfRangeException();
125 /// Creates a new memory from a memory manager that provides specific method implementations beginning
126 /// at 0 index and ending at 'end' index (exclusive).
128 /// <param name="manager">The memory manager.</param>
129 /// <param name="length">The number of items in the memory.</param>
130 /// <exception cref="System.ArgumentOutOfRangeException">
131 /// Thrown when the specified <paramref name="length"/> is negative.
133 /// <remarks>For internal infrastructure only</remarks>
134 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
135 internal Memory(MemoryManager
<T
> manager
, int length
)
137 Debug
.Assert(manager
!= null);
140 ThrowHelper
.ThrowArgumentOutOfRangeException();
148 /// Creates a new memory from a memory manager that provides specific method implementations beginning
149 /// at 'start' index and ending at 'end' index (exclusive).
151 /// <param name="manager">The memory manager.</param>
152 /// <param name="start">The index at which to begin the memory.</param>
153 /// <param name="length">The number of items in the memory.</param>
154 /// <exception cref="System.ArgumentOutOfRangeException">
155 /// Thrown when the specified <paramref name="start"/> or <paramref name="length"/> is negative.
157 /// <remarks>For internal infrastructure only</remarks>
158 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
159 internal Memory(MemoryManager
<T
> manager
, int start
, int length
)
161 Debug
.Assert(manager
!= null);
163 if (length
< 0 || start
< 0)
164 ThrowHelper
.ThrowArgumentOutOfRangeException();
171 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
172 internal Memory(object? obj
, int start
, int length
)
174 // No validation performed in release builds; caller must provide any necessary validation.
176 // 'obj is T[]' below also handles things like int[] <-> uint[] being convertible
177 Debug
.Assert((obj
== null)
178 || (typeof(T
) == typeof(char) && obj
is string)
179 #if FEATURE_UTF8STRING
180 || ((typeof(T
) == typeof(byte) || typeof(T
) == typeof(Char8
)) && obj
is Utf8String
)
181 #endif // FEATURE_UTF8STRING
183 || (obj
is MemoryManager
<T
>));
191 /// Defines an implicit conversion of an array to a <see cref="Memory{T}"/>
193 public static implicit operator Memory
<T
>(T
[]? array
) => new Memory
<T
>(array
);
196 /// Defines an implicit conversion of a <see cref="ArraySegment{T}"/> to a <see cref="Memory{T}"/>
198 public static implicit operator Memory
<T
>(ArraySegment
<T
> segment
) => new Memory
<T
>(segment
.Array
, segment
.Offset
, segment
.Count
);
201 /// Defines an implicit conversion of a <see cref="Memory{T}"/> to a <see cref="ReadOnlyMemory{T}"/>
203 public static implicit operator ReadOnlyMemory
<T
>(Memory
<T
> memory
) =>
204 Unsafe
.As
<Memory
<T
>, ReadOnlyMemory
<T
>>(ref memory
);
207 /// Returns an empty <see cref="Memory{T}"/>
209 public static Memory
<T
> Empty
=> default;
212 /// The number of items in the memory.
214 public int Length
=> _length
;
217 /// Returns true if Length is 0.
219 public bool IsEmpty
=> _length
== 0;
222 /// For <see cref="Memory{Char}"/>, returns a new instance of string that represents the characters pointed to by the memory.
223 /// Otherwise, returns a <see cref="string"/> with the name of the type and the number of elements.
225 public override string ToString()
227 if (typeof(T
) == typeof(char))
229 return (_object
is string str
) ? str
.Substring(_index
, _length
) : Span
.ToString();
231 #if FEATURE_UTF8STRING
232 else if (typeof(T
) == typeof(Char8
))
234 // TODO_UTF8STRING: Call into optimized transcoding routine when it's available.
236 return Encoding
.UTF8
.GetString(new ReadOnlySpan
<byte>(ref Unsafe
.As
<T
, byte>(ref MemoryMarshal
.GetReference(span
)), span
.Length
));
238 #endif // FEATURE_UTF8STRING
239 return string.Format("System.Memory<{0}>[{1}]", typeof(T
).Name
, _length
);
243 /// Forms a slice out of the given memory, beginning at 'start'.
245 /// <param name="start">The index at which to begin this slice.</param>
246 /// <exception cref="System.ArgumentOutOfRangeException">
247 /// Thrown when the specified <paramref name="start"/> index is not in range (<0 or >Length).
249 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
250 public Memory
<T
> Slice(int start
)
252 if ((uint)start
> (uint)_length
)
254 ThrowHelper
.ThrowArgumentOutOfRangeException(ExceptionArgument
.start
);
257 // It is expected for _index + start to be negative if the memory is already pre-pinned.
258 return new Memory
<T
>(_object
, _index
+ start
, _length
- start
);
262 /// Forms a slice out of the given memory, beginning at 'start', of given length
264 /// <param name="start">The index at which to begin this slice.</param>
265 /// <param name="length">The desired length for the slice (exclusive).</param>
266 /// <exception cref="System.ArgumentOutOfRangeException">
267 /// Thrown when the specified <paramref name="start"/> or end index is not in range (<0 or >Length).
269 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
270 public Memory
<T
> Slice(int start
, int length
)
273 // See comment in Span<T>.Slice for how this works.
274 if ((ulong)(uint)start
+ (ulong)(uint)length
> (ulong)(uint)_length
)
275 ThrowHelper
.ThrowArgumentOutOfRangeException();
277 if ((uint)start
> (uint)_length
|| (uint)length
> (uint)(_length
- start
))
278 ThrowHelper
.ThrowArgumentOutOfRangeException();
281 // It is expected for _index + start to be negative if the memory is already pre-pinned.
282 return new Memory
<T
>(_object
, _index
+ start
, length
);
286 /// Returns a span from the memory.
288 public unsafe Span
<T
> Span
290 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
293 // This property getter has special support for returning a mutable Span<char> that wraps
294 // an immutable String instance. This is obviously a dangerous feature and breaks type safety.
295 // However, we need to handle the case where a ReadOnlyMemory<char> was created from a string
296 // and then cast to a Memory<T>. Such a cast can only be done with unsafe or marshaling code,
297 // in which case that's the dangerous operation performed by the dev, and we're just following
298 // suit here to make it work as best as possible.
300 ref T refToReturn
= ref Unsafe
.AsRef
<T
>(null);
301 int lengthOfUnderlyingSpan
= 0;
303 // Copy this field into a local so that it can't change out from under us mid-operation.
305 object? tmpObject
= _object
;
306 if (tmpObject
!= null)
308 if (typeof(T
) == typeof(char) && tmpObject
.GetType() == typeof(string))
310 // Special-case string since it's the most common for ROM<char>.
312 refToReturn
= ref Unsafe
.As
<char, T
>(ref Unsafe
.As
<string>(tmpObject
).GetRawStringData());
313 lengthOfUnderlyingSpan
= Unsafe
.As
<string>(tmpObject
).Length
;
315 #if FEATURE_UTF8STRING
316 else if ((typeof(T
) == typeof(byte) || typeof(T
) == typeof(Char8
)) && tmpObject
.GetType() == typeof(Utf8String
))
318 refToReturn
= ref Unsafe
.As
<byte, T
>(ref Unsafe
.As
<Utf8String
>(tmpObject
).DangerousGetMutableReference());
319 lengthOfUnderlyingSpan
= Unsafe
.As
<Utf8String
>(tmpObject
).Length
;
321 #endif // FEATURE_UTF8STRING
322 else if (RuntimeHelpers
.ObjectHasComponentSize(tmpObject
))
324 // We know the object is not null, it's not a string, and it is variable-length. The only
325 // remaining option is for it to be a T[] (or a U[] which is blittable to T[], like int[]
326 // and uint[]). Otherwise somebody used private reflection to set this field, and we're not
327 // too worried about type safety violations at this point.
329 // 'tmpObject is T[]' below also handles things like int[] <-> uint[] being convertible
330 Debug
.Assert(tmpObject
is T
[]);
332 refToReturn
= ref Unsafe
.As
<byte, T
>(ref Unsafe
.As
<T
[]>(tmpObject
).GetRawSzArrayData());
333 lengthOfUnderlyingSpan
= Unsafe
.As
<T
[]>(tmpObject
).Length
;
337 // We know the object is not null, and it's not variable-length, so it must be a MemoryManager<T>.
338 // Otherwise somebody used private reflection to set this field, and we're not too worried about
339 // type safety violations at that point. Note that it can't be a MemoryManager<U>, even if U and
340 // T are blittable (e.g., MemoryManager<int> to MemoryManager<uint>), since there exists no
341 // constructor or other public API which would allow such a conversion.
343 Debug
.Assert(tmpObject
is MemoryManager
<T
>);
344 Span
<T
> memoryManagerSpan
= Unsafe
.As
<MemoryManager
<T
>>(tmpObject
).GetSpan();
345 refToReturn
= ref MemoryMarshal
.GetReference(memoryManagerSpan
);
346 lengthOfUnderlyingSpan
= memoryManagerSpan
.Length
;
349 // If the Memory<T> or ReadOnlyMemory<T> instance is torn, this property getter has undefined behavior.
350 // We try to detect this condition and throw an exception, but it's possible that a torn struct might
351 // appear to us to be valid, and we'll return an undesired span. Such a span is always guaranteed at
352 // least to be in-bounds when compared with the original Memory<T> instance, so using the span won't
355 nuint desiredStartIndex
= (uint)_index
& (uint)ReadOnlyMemory
<T
>.RemoveFlagsBitMask
;
356 int desiredLength
= _length
;
359 // See comment in Span<T>.Slice for how this works.
360 if ((ulong)desiredStartIndex
+ (ulong)(uint)desiredLength
> (ulong)(uint)lengthOfUnderlyingSpan
)
362 ThrowHelper
.ThrowArgumentOutOfRangeException();
365 if ((uint)desiredStartIndex
> (uint)lengthOfUnderlyingSpan
|| (uint)desiredLength
> (uint)(lengthOfUnderlyingSpan
- desiredStartIndex
))
367 ThrowHelper
.ThrowArgumentOutOfRangeException();
371 refToReturn
= ref Unsafe
.Add(ref refToReturn
, (IntPtr
)(void*)desiredStartIndex
);
372 lengthOfUnderlyingSpan
= desiredLength
;
375 return new Span
<T
>(ref refToReturn
, lengthOfUnderlyingSpan
);
380 /// Copies the contents of the memory into the destination. If the source
381 /// and destination overlap, this method behaves as if the original values are in
382 /// a temporary location before the destination is overwritten.
384 /// <param name="destination">The Memory to copy items into.</param>
385 /// <exception cref="System.ArgumentException">
386 /// Thrown when the destination is shorter than the source.
389 public void CopyTo(Memory
<T
> destination
) => Span
.CopyTo(destination
.Span
);
392 /// Copies the contents of the memory into the destination. If the source
393 /// and destination overlap, this method behaves as if the original values are in
394 /// a temporary location before the destination is overwritten.
396 /// <returns>If the destination is shorter than the source, this method
397 /// return false and no data is written to the destination.</returns>
399 /// <param name="destination">The span to copy items into.</param>
400 public bool TryCopyTo(Memory
<T
> destination
) => Span
.TryCopyTo(destination
.Span
);
403 /// Creates a handle for the memory.
404 /// The GC will not move the memory until the returned <see cref="MemoryHandle"/>
405 /// is disposed, enabling taking and using the memory's address.
406 /// <exception cref="System.ArgumentException">
407 /// An instance with nonprimitive (non-blittable) members cannot be pinned.
410 public unsafe MemoryHandle
Pin()
412 // Just like the Span property getter, we have special support for a mutable Memory<char>
413 // that wraps an immutable String instance. This might happen if a caller creates an
414 // immutable ROM<char> wrapping a String, then uses Unsafe.As to create a mutable M<char>.
415 // This needs to work, however, so that code that uses a single Memory<char> field to store either
416 // a readable ReadOnlyMemory<char> or a writable Memory<char> can still be pinned and
417 // used for interop purposes.
419 // It's possible that the below logic could result in an AV if the struct
420 // is torn. This is ok since the caller is expecting to use raw pointers,
421 // and we're not required to keep this as safe as the other Span-based APIs.
423 object? tmpObject
= _object
;
424 if (tmpObject
!= null)
426 if (typeof(T
) == typeof(char) && tmpObject
is string s
)
428 GCHandle handle
= GCHandle
.Alloc(tmpObject
, GCHandleType
.Pinned
);
429 ref char stringData
= ref Unsafe
.Add(ref s
.GetRawStringData(), _index
);
430 return new MemoryHandle(Unsafe
.AsPointer(ref stringData
), handle
);
432 #if FEATURE_UTF8STRING
433 else if ((typeof(T
) == typeof(byte) || typeof(T
) == typeof(Char8
)) && tmpObject
is Utf8String utf8String
)
435 GCHandle handle
= GCHandle
.Alloc(tmpObject
, GCHandleType
.Pinned
);
436 ref byte stringData
= ref utf8String
.DangerousGetMutableReference(_index
);
437 return new MemoryHandle(Unsafe
.AsPointer(ref stringData
), handle
);
439 #endif // FEATURE_UTF8STRING
440 else if (RuntimeHelpers
.ObjectHasComponentSize(tmpObject
))
442 // 'tmpObject is T[]' below also handles things like int[] <-> uint[] being convertible
443 Debug
.Assert(tmpObject
is T
[]);
445 // Array is already pre-pinned
448 void* pointer
= Unsafe
.Add
<T
>(Unsafe
.AsPointer(ref Unsafe
.As
<T
[]>(tmpObject
).GetRawSzArrayData()), _index
& ReadOnlyMemory
<T
>.RemoveFlagsBitMask
);
449 return new MemoryHandle(pointer
);
453 GCHandle handle
= GCHandle
.Alloc(tmpObject
, GCHandleType
.Pinned
);
454 void* pointer
= Unsafe
.Add
<T
>(Unsafe
.AsPointer(ref Unsafe
.As
<T
[]>(tmpObject
).GetRawSzArrayData()), _index
);
455 return new MemoryHandle(pointer
, handle
);
460 Debug
.Assert(tmpObject
is MemoryManager
<T
>);
461 return Unsafe
.As
<MemoryManager
<T
>>(tmpObject
).Pin(_index
);
469 /// Copies the contents from the memory into a new array. This heap
470 /// allocates, so should generally be avoided, however it is sometimes
471 /// necessary to bridge the gap with APIs written in terms of arrays.
473 public T
[] ToArray() => Span
.ToArray();
476 /// Determines whether the specified object is equal to the current object.
477 /// Returns true if the object is Memory or ReadOnlyMemory and if both objects point to the same array and have the same length.
479 [EditorBrowsable(EditorBrowsableState
.Never
)]
480 public override bool Equals(object? obj
)
482 if (obj
is ReadOnlyMemory
<T
>)
484 return ((ReadOnlyMemory
<T
>)obj
).Equals(this);
486 else if (obj
is Memory
<T
> memory
)
488 return Equals(memory
);
497 /// Returns true if the memory points to the same array and has the same length. Note that
498 /// this does *not* check to see if the *contents* are equal.
500 public bool Equals(Memory
<T
> other
)
503 _object
== other
._object
&&
504 _index
== other
._index
&&
505 _length
== other
._length
;
509 /// Serves as the default hash function.
511 [EditorBrowsable(EditorBrowsableState
.Never
)]
512 public override int GetHashCode()
514 // We use RuntimeHelpers.GetHashCode instead of Object.GetHashCode because the hash
515 // code is based on object identity and referential equality, not deep equality (as common with string).
516 return (_object
!= null) ? HashCode
.Combine(RuntimeHelpers
.GetHashCode(_object
), _index
, _length
) : 0;