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;
9 using EditorBrowsableAttribute = System.ComponentModel.EditorBrowsableAttribute;
10 using EditorBrowsableState = System.ComponentModel.EditorBrowsableState;
11 #if !FEATURE_PORTABLE_SPAN
12 using Internal.Runtime.CompilerServices;
13 #endif // FEATURE_PORTABLE_SPAN
18 /// Memory represents a contiguous region of arbitrary memory similar to <see cref="Span{T}"/>.
19 /// Unlike <see cref="Span{T}"/>, it is not a byref-like type.
21 [DebuggerTypeProxy(typeof(MemoryDebugView<>))]
22 [DebuggerDisplay("{ToString(),raw}")]
23 public readonly struct Memory<T>
25 // NOTE: With the current implementation, Memory<T> and ReadOnlyMemory<T> must have the same layout,
26 // as code uses Unsafe.As to cast between them.
28 // The highest order bit of _index is used to discern whether _object is an array/string or an owned memory
29 // if (_index >> 31) == 1, object _object is an MemoryManager<T>
30 // else, object _object is a T[] or a string.
31 // if (_length >> 31) == 1, _object is a pre-pinned array, so Pin() will not allocate a new GCHandle
32 // else, Pin() needs to allocate a new GCHandle to pin the object.
33 // It can only be a string if the Memory<T> was created by
34 // using unsafe / marshaling code to reinterpret a ReadOnlyMemory<char> wrapped around a string as
36 private readonly object _object;
37 private readonly int _index;
38 private readonly int _length;
40 private const int RemoveFlagsBitMask = 0x7FFFFFFF;
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[]))
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[]))
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[]))
107 ThrowHelper.ThrowArrayTypeMismatchException();
108 if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start))
109 ThrowHelper.ThrowArgumentOutOfRangeException();
117 /// Creates a new memory from a memory manager that provides specific method implementations beginning
118 /// at 0 index and ending at 'end' index (exclusive).
120 /// <param name="manager">The memory manager.</param>
121 /// <param name="length">The number of items in the memory.</param>
122 /// <exception cref="System.ArgumentOutOfRangeException">
123 /// Thrown when the specified <paramref name="length"/> is negative.
125 /// <remarks>For internal infrastructure only</remarks>
126 [MethodImpl(MethodImplOptions.AggressiveInlining)]
127 internal Memory(MemoryManager<T> manager, int length)
129 Debug.Assert(manager != null);
132 ThrowHelper.ThrowArgumentOutOfRangeException();
135 _index = (1 << 31); // Mark as MemoryManager type
136 // Before using _index, check if _index < 0, then 'and' it with RemoveFlagsBitMask
141 /// Creates a new memory from a memory manager that provides specific method implementations beginning
142 /// at 'start' index and ending at 'end' index (exclusive).
144 /// <param name="manager">The memory manager.</param>
145 /// <param name="start">The index at which to begin the memory.</param>
146 /// <param name="length">The number of items in the memory.</param>
147 /// <exception cref="System.ArgumentOutOfRangeException">
148 /// Thrown when the specified <paramref name="start"/> or <paramref name="length"/> is negative.
150 /// <remarks>For internal infrastructure only</remarks>
151 [MethodImpl(MethodImplOptions.AggressiveInlining)]
152 internal Memory(MemoryManager<T> manager, int start, int length)
154 Debug.Assert(manager != null);
156 if (length < 0 || start < 0)
157 ThrowHelper.ThrowArgumentOutOfRangeException();
160 _index = start | (1 << 31); // Mark as MemoryManager type
161 // Before using _index, check if _index < 0, then 'and' it with RemoveFlagsBitMask
166 /// Creates a new memory over the portion of the pre-pinned target array beginning
167 /// at 'start' index and ending at 'end' index (exclusive).
169 /// <param name="array">The pre-pinned target array.</param>
170 /// <param name="start">The index at which to begin the memory.</param>
171 /// <param name="length">The number of items in the memory.</param>
172 /// <remarks>Returns default when <paramref name="array"/> is null.</remarks>
173 /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="array"/> is covariant and array's type is not exactly T[].</exception>
174 /// <exception cref="System.ArgumentOutOfRangeException">
175 /// Thrown when the specified <paramref name="start"/> or end index is not in the range (<0 or >=Length).
177 [EditorBrowsable(EditorBrowsableState.Never)]
178 [MethodImpl(MethodImplOptions.AggressiveInlining)]
179 public static Memory<T> CreateFromPinnedArray(T[] array, int start, int length)
183 if (start != 0 || length != 0)
184 ThrowHelper.ThrowArgumentOutOfRangeException();
187 if (default(T) == null && array.GetType() != typeof(T[]))
188 ThrowHelper.ThrowArrayTypeMismatchException();
189 if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start))
190 ThrowHelper.ThrowArgumentOutOfRangeException();
192 // Before using _length, check if _length < 0, then 'and' it with RemoveFlagsBitMask
193 return new Memory<T>((object)array, start, length | (1 << 31));
196 [MethodImpl(MethodImplOptions.AggressiveInlining)]
197 internal Memory(object obj, int start, int length)
199 // No validation performed; caller must provide any necessary validation.
206 /// Defines an implicit conversion of an array to a <see cref="Memory{T}"/>
208 public static implicit operator Memory<T>(T[] array) => new Memory<T>(array);
211 /// Defines an implicit conversion of a <see cref="ArraySegment{T}"/> to a <see cref="Memory{T}"/>
213 public static implicit operator Memory<T>(ArraySegment<T> segment) => new Memory<T>(segment.Array, segment.Offset, segment.Count);
216 /// Defines an implicit conversion of a <see cref="Memory{T}"/> to a <see cref="ReadOnlyMemory{T}"/>
218 public static implicit operator ReadOnlyMemory<T>(Memory<T> memory) =>
219 Unsafe.As<Memory<T>, ReadOnlyMemory<T>>(ref memory);
222 /// Returns an empty <see cref="Memory{T}"/>
224 public static Memory<T> Empty => default;
227 /// The number of items in the memory.
229 public int Length => _length & RemoveFlagsBitMask;
232 /// Returns true if Length is 0.
234 public bool IsEmpty => (_length & RemoveFlagsBitMask) == 0;
237 /// For <see cref="Memory{Char}"/>, returns a new instance of string that represents the characters pointed to by the memory.
238 /// Otherwise, returns a <see cref="string"/> with the name of the type and the number of elements.
240 public override string ToString()
242 if (typeof(T) == typeof(char))
244 return (_object is string str) ? str.Substring(_index, _length & RemoveFlagsBitMask) : Span.ToString();
246 return string.Format("System.Memory<{0}>[{1}]", typeof(T).Name, _length & RemoveFlagsBitMask);
250 /// Forms a slice out of the given memory, beginning at 'start'.
252 /// <param name="start">The index at which to begin this slice.</param>
253 /// <exception cref="System.ArgumentOutOfRangeException">
254 /// Thrown when the specified <paramref name="start"/> index is not in range (<0 or >=Length).
256 [MethodImpl(MethodImplOptions.AggressiveInlining)]
257 public Memory<T> Slice(int start)
259 int actualLength = _length & RemoveFlagsBitMask;
260 if ((uint)start > (uint)actualLength)
262 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
265 return new Memory<T>(_object, _index + start, actualLength - start);
269 /// Forms a slice out of the given memory, beginning at 'start', of given length
271 /// <param name="start">The index at which to begin this slice.</param>
272 /// <param name="length">The desired length for the slice (exclusive).</param>
273 /// <exception cref="System.ArgumentOutOfRangeException">
274 /// Thrown when the specified <paramref name="start"/> or end index is not in range (<0 or >=Length).
276 [MethodImpl(MethodImplOptions.AggressiveInlining)]
277 public Memory<T> Slice(int start, int length)
279 int actualLength = _length & RemoveFlagsBitMask;
280 if ((uint)start > (uint)actualLength || (uint)length > (uint)(actualLength - start))
282 ThrowHelper.ThrowArgumentOutOfRangeException();
285 return new Memory<T>(_object, _index + start, length);
289 /// Returns a span from the memory.
293 [MethodImpl(MethodImplOptions.AggressiveInlining)]
298 Debug.Assert(_length >= 0);
299 Debug.Assert(_object != null);
300 return ((MemoryManager<T>)_object).GetSpan().Slice(_index & RemoveFlagsBitMask, _length);
302 else if (typeof(T) == typeof(char) && _object is string s)
304 Debug.Assert(_length >= 0);
305 // This is dangerous, returning a writable span for a string that should be immutable.
306 // However, we need to handle the case where a ReadOnlyMemory<char> was created from a string
307 // and then cast to a Memory<T>. Such a cast can only be done with unsafe or marshaling code,
308 // in which case that's the dangerous operation performed by the dev, and we're just following
309 // suit here to make it work as best as possible.
310 #if FEATURE_PORTABLE_SPAN
311 return new Span<T>(Unsafe.As<Pinnable<T>>(s), MemoryExtensions.StringAdjustment, s.Length).Slice(_index, _length);
313 return new Span<T>(ref Unsafe.As<char, T>(ref s.GetRawStringData()), s.Length).Slice(_index, _length);
314 #endif // FEATURE_PORTABLE_SPAN
316 else if (_object != null)
318 return new Span<T>((T[])_object, _index, _length & RemoveFlagsBitMask);
328 /// Copies the contents of the memory into the destination. If the source
329 /// and destination overlap, this method behaves as if the original values are in
330 /// a temporary location before the destination is overwritten.
332 /// <param name="destination">The Memory to copy items into.</param>
333 /// <exception cref="System.ArgumentException">
334 /// Thrown when the destination is shorter than the source.
337 public void CopyTo(Memory<T> destination) => Span.CopyTo(destination.Span);
340 /// Copies the contents of the memory into the destination. If the source
341 /// and destination overlap, this method behaves as if the original values are in
342 /// a temporary location before the destination is overwritten.
344 /// <returns>If the destination is shorter than the source, this method
345 /// return false and no data is written to the destination.</returns>
347 /// <param name="destination">The span to copy items into.</param>
348 public bool TryCopyTo(Memory<T> destination) => Span.TryCopyTo(destination.Span);
351 /// Creates a handle for the memory.
352 /// The GC will not move the array until the returned <see cref="MemoryHandle"/>
353 /// is disposed, enabling taking and using the memory's address.
355 public unsafe MemoryHandle Pin()
359 Debug.Assert(_object != null);
360 return ((MemoryManager<T>)_object).Pin((_index & RemoveFlagsBitMask));
362 else if (typeof(T) == typeof(char) && _object is string s)
364 // This case can only happen if a ReadOnlyMemory<char> was created around a string
365 // and then that was cast to a Memory<char> using unsafe / marshaling code. This needs
366 // to work, however, so that code that uses a single Memory<char> field to store either
367 // a readable ReadOnlyMemory<char> or a writable Memory<char> can still be pinned and
368 // used for interop purposes.
369 GCHandle handle = GCHandle.Alloc(s, GCHandleType.Pinned);
370 #if FEATURE_PORTABLE_SPAN
371 void* pointer = Unsafe.Add<T>((void*)handle.AddrOfPinnedObject(), _index);
373 void* pointer = Unsafe.Add<T>(Unsafe.AsPointer(ref s.GetRawStringData()), _index);
374 #endif // FEATURE_PORTABLE_SPAN
375 return new MemoryHandle(pointer, handle);
377 else if (_object is T[] array)
379 GCHandle handle = _length < 0 ? default : GCHandle.Alloc(array, GCHandleType.Pinned);
380 #if FEATURE_PORTABLE_SPAN
381 void* pointer = Unsafe.Add<T>((void*)handle.AddrOfPinnedObject(), _index);
383 void* pointer = Unsafe.Add<T>(Unsafe.AsPointer(ref array.GetRawSzArrayData()), _index);
384 #endif // FEATURE_PORTABLE_SPAN
385 return new MemoryHandle(pointer, handle);
391 /// Copies the contents from the memory into a new array. This heap
392 /// allocates, so should generally be avoided, however it is sometimes
393 /// necessary to bridge the gap with APIs written in terms of arrays.
395 public T[] ToArray() => Span.ToArray();
398 /// Determines whether the specified object is equal to the current object.
399 /// Returns true if the object is Memory or ReadOnlyMemory and if both objects point to the same array and have the same length.
401 [EditorBrowsable(EditorBrowsableState.Never)]
402 public override bool Equals(object obj)
404 if (obj is ReadOnlyMemory<T>)
406 return ((ReadOnlyMemory<T>)obj).Equals(this);
408 else if (obj is Memory<T> memory)
410 return Equals(memory);
419 /// Returns true if the memory points to the same array and has the same length. Note that
420 /// this does *not* check to see if the *contents* are equal.
422 public bool Equals(Memory<T> other)
425 _object == other._object &&
426 _index == other._index &&
427 _length == other._length;
431 /// Serves as the default hash function.
433 [EditorBrowsable(EditorBrowsableState.Never)]
434 public override int GetHashCode()
436 return _object != null ? CombineHashCodes(_object.GetHashCode(), _index.GetHashCode(), _length.GetHashCode()) : 0;
439 private static int CombineHashCodes(int left, int right)
441 return ((left << 5) + left) ^ right;
444 private static int CombineHashCodes(int h1, int h2, int h3)
446 return CombineHashCodes(CombineHashCodes(h1, h2), h3);