Remove MemoryManager.Length (#17498)
[platform/upstream/coreclr.git] / src / mscorlib / shared / System / Memory.cs
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.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
14
15 namespace System
16 {
17     /// <summary>
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.
20     /// </summary>
21     [DebuggerTypeProxy(typeof(MemoryDebugView<>))]
22     [DebuggerDisplay("{ToString(),raw}")]
23     public readonly struct Memory<T>
24     {
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.
27
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
35         // a Memory<T>.
36         private readonly object _object;
37         private readonly int _index;
38         private readonly int _length;
39
40         private const int RemoveFlagsBitMask = 0x7FFFFFFF;
41
42         /// <summary>
43         /// Creates a new memory over the entirety of the target array.
44         /// </summary>
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)
50         {
51             if (array == null)
52             {
53                 this = default;
54                 return; // returns default
55             }
56             if (default(T) == null && array.GetType() != typeof(T[]))
57                 ThrowHelper.ThrowArrayTypeMismatchException();
58
59             _object = array;
60             _index = 0;
61             _length = array.Length;
62         }
63
64         [MethodImpl(MethodImplOptions.AggressiveInlining)]
65         internal Memory(T[] array, int start)
66         {
67             if (array == null)
68             {
69                 if (start != 0)
70                     ThrowHelper.ThrowArgumentOutOfRangeException();
71                 this = default;
72                 return; // returns default
73             }
74             if (default(T) == null && array.GetType() != typeof(T[]))
75                 ThrowHelper.ThrowArrayTypeMismatchException();
76             if ((uint)start > (uint)array.Length)
77                 ThrowHelper.ThrowArgumentOutOfRangeException();
78
79             _object = array;
80             _index = start;
81             _length = array.Length - start;
82         }
83
84         /// <summary>
85         /// Creates a new memory over the portion of the target array beginning
86         /// at 'start' index and ending at 'end' index (exclusive).
87         /// </summary>
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 (&lt;0 or &gt;=Length).
95         /// </exception>
96         [MethodImpl(MethodImplOptions.AggressiveInlining)]
97         public Memory(T[] array, int start, int length)
98         {
99             if (array == null)
100             {
101                 if (start != 0 || length != 0)
102                     ThrowHelper.ThrowArgumentOutOfRangeException();
103                 this = default;
104                 return; // returns default
105             }
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();
110
111             _object = array;
112             _index = start;
113             _length = length;
114         }
115
116         /// <summary>
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).
119         /// </summary>
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.
124         /// </exception>
125         /// <remarks>For internal infrastructure only</remarks>
126         [MethodImpl(MethodImplOptions.AggressiveInlining)]
127         internal Memory(MemoryManager<T> manager, int length)
128         {
129             Debug.Assert(manager != null);
130
131             if (length < 0)
132                 ThrowHelper.ThrowArgumentOutOfRangeException();
133
134             _object = manager;
135             _index = (1 << 31); // Mark as MemoryManager type
136             // Before using _index, check if _index < 0, then 'and' it with RemoveFlagsBitMask
137             _length = length;
138         }
139
140         /// <summary>
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).
143         /// </summary>
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.
149         /// </exception>
150         /// <remarks>For internal infrastructure only</remarks>
151         [MethodImpl(MethodImplOptions.AggressiveInlining)]
152         internal Memory(MemoryManager<T> manager, int start, int length)
153         {
154             Debug.Assert(manager != null);
155
156             if (length < 0 || start < 0)
157                 ThrowHelper.ThrowArgumentOutOfRangeException();
158
159             _object = manager;
160             _index = start | (1 << 31); // Mark as MemoryManager type
161             // Before using _index, check if _index < 0, then 'and' it with RemoveFlagsBitMask
162             _length = length;
163         }
164
165         /// <summary>
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).
168         /// </summary>
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 (&lt;0 or &gt;=Length).
176         /// </exception>
177         [EditorBrowsable(EditorBrowsableState.Never)]
178         [MethodImpl(MethodImplOptions.AggressiveInlining)]
179         public static Memory<T> CreateFromPinnedArray(T[] array, int start, int length)
180         {
181             if (array == null)
182             {
183                 if (start != 0 || length != 0)
184                     ThrowHelper.ThrowArgumentOutOfRangeException();
185                 return default;
186             }
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();
191
192             // Before using _length, check if _length < 0, then 'and' it with RemoveFlagsBitMask
193             return new Memory<T>((object)array, start, length | (1 << 31));
194         }
195
196         [MethodImpl(MethodImplOptions.AggressiveInlining)]
197         internal Memory(object obj, int start, int length)
198         {
199             // No validation performed; caller must provide any necessary validation.
200             _object = obj;
201             _index = start;
202             _length = length;
203         }
204
205         /// <summary>
206         /// Defines an implicit conversion of an array to a <see cref="Memory{T}"/>
207         /// </summary>
208         public static implicit operator Memory<T>(T[] array) => new Memory<T>(array);
209
210         /// <summary>
211         /// Defines an implicit conversion of a <see cref="ArraySegment{T}"/> to a <see cref="Memory{T}"/>
212         /// </summary>
213         public static implicit operator Memory<T>(ArraySegment<T> segment) => new Memory<T>(segment.Array, segment.Offset, segment.Count);
214
215         /// <summary>
216         /// Defines an implicit conversion of a <see cref="Memory{T}"/> to a <see cref="ReadOnlyMemory{T}"/>
217         /// </summary>
218         public static implicit operator ReadOnlyMemory<T>(Memory<T> memory) =>
219             Unsafe.As<Memory<T>, ReadOnlyMemory<T>>(ref memory);
220
221         /// <summary>
222         /// Returns an empty <see cref="Memory{T}"/>
223         /// </summary>
224         public static Memory<T> Empty => default;
225
226         /// <summary>
227         /// The number of items in the memory.
228         /// </summary>
229         public int Length => _length & RemoveFlagsBitMask;
230
231         /// <summary>
232         /// Returns true if Length is 0.
233         /// </summary>
234         public bool IsEmpty => (_length & RemoveFlagsBitMask) == 0;
235
236         /// <summary>
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.
239         /// </summary>
240         public override string ToString()
241         {
242             if (typeof(T) == typeof(char))
243             {
244                 return (_object is string str) ? str.Substring(_index, _length & RemoveFlagsBitMask) : Span.ToString();
245             }
246             return string.Format("System.Memory<{0}>[{1}]", typeof(T).Name, _length & RemoveFlagsBitMask);
247         }
248
249         /// <summary>
250         /// Forms a slice out of the given memory, beginning at 'start'.
251         /// </summary>
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 (&lt;0 or &gt;=Length).
255         /// </exception>
256         [MethodImpl(MethodImplOptions.AggressiveInlining)]
257         public Memory<T> Slice(int start)
258         {
259             int actualLength = _length & RemoveFlagsBitMask;
260             if ((uint)start > (uint)actualLength)
261             {
262                 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
263             }
264
265             return new Memory<T>(_object, _index + start, actualLength - start);
266         }
267
268         /// <summary>
269         /// Forms a slice out of the given memory, beginning at 'start', of given length
270         /// </summary>
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 (&lt;0 or &gt;=Length).
275         /// </exception>
276         [MethodImpl(MethodImplOptions.AggressiveInlining)]
277         public Memory<T> Slice(int start, int length)
278         {
279             int actualLength = _length & RemoveFlagsBitMask;
280             if ((uint)start > (uint)actualLength || (uint)length > (uint)(actualLength - start))
281             {
282                 ThrowHelper.ThrowArgumentOutOfRangeException();
283             }
284
285             return new Memory<T>(_object, _index + start, length);
286         }
287
288         /// <summary>
289         /// Returns a span from the memory.
290         /// </summary>
291         public Span<T> Span
292         {
293             [MethodImpl(MethodImplOptions.AggressiveInlining)]
294             get
295             {
296                 if (_index < 0)
297                 {
298                     Debug.Assert(_length >= 0);
299                     Debug.Assert(_object != null);
300                     return ((MemoryManager<T>)_object).GetSpan().Slice(_index & RemoveFlagsBitMask, _length);
301                 }
302                 else if (typeof(T) == typeof(char) && _object is string s)
303                 {
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);
312 #else
313                     return new Span<T>(ref Unsafe.As<char, T>(ref s.GetRawStringData()), s.Length).Slice(_index, _length);
314 #endif // FEATURE_PORTABLE_SPAN
315                 }
316                 else if (_object != null)
317                 {
318                     return new Span<T>((T[])_object, _index, _length & RemoveFlagsBitMask);
319                 }
320                 else
321                 {
322                     return default;
323                 }
324             }
325         }
326
327         /// <summary>
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.
331         ///
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.
335         /// </exception>
336         /// </summary>
337         public void CopyTo(Memory<T> destination) => Span.CopyTo(destination.Span);
338
339         /// <summary>
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.
343         ///
344         /// <returns>If the destination is shorter than the source, this method
345         /// return false and no data is written to the destination.</returns>
346         /// </summary>
347         /// <param name="destination">The span to copy items into.</param>
348         public bool TryCopyTo(Memory<T> destination) => Span.TryCopyTo(destination.Span);
349
350         /// <summary>
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.
354         /// </summary>
355         public unsafe MemoryHandle Pin()
356         {
357             if (_index < 0)
358             {
359                 Debug.Assert(_object != null);
360                 return ((MemoryManager<T>)_object).Pin((_index & RemoveFlagsBitMask));
361             }
362             else if (typeof(T) == typeof(char) && _object is string s)
363             {
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);
372 #else
373                 void* pointer = Unsafe.Add<T>(Unsafe.AsPointer(ref s.GetRawStringData()), _index);
374 #endif // FEATURE_PORTABLE_SPAN
375                 return new MemoryHandle(pointer, handle);
376             }
377             else if (_object is T[] array)
378             {
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);
382 #else
383                 void* pointer = Unsafe.Add<T>(Unsafe.AsPointer(ref array.GetRawSzArrayData()), _index);
384 #endif // FEATURE_PORTABLE_SPAN
385                 return new MemoryHandle(pointer, handle);
386             }
387             return default;
388         }
389
390         /// <summary>
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.
394         /// </summary>
395         public T[] ToArray() => Span.ToArray();
396
397         /// <summary>
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.
400         /// </summary>
401         [EditorBrowsable(EditorBrowsableState.Never)]
402         public override bool Equals(object obj)
403         {
404             if (obj is ReadOnlyMemory<T>)
405             {
406                 return ((ReadOnlyMemory<T>)obj).Equals(this);
407             }
408             else if (obj is Memory<T> memory)
409             {
410                 return Equals(memory);
411             }
412             else
413             {
414                 return false;
415             }
416         }
417
418         /// <summary>
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.
421         /// </summary>
422         public bool Equals(Memory<T> other)
423         {
424             return
425                 _object == other._object &&
426                 _index == other._index &&
427                 _length == other._length;
428         }
429
430         /// <summary>
431         /// Serves as the default hash function.
432         /// </summary>
433         [EditorBrowsable(EditorBrowsableState.Never)]
434         public override int GetHashCode()
435         {
436             return _object != null ? CombineHashCodes(_object.GetHashCode(), _index.GetHashCode(), _length.GetHashCode()) : 0;
437         }
438
439         private static int CombineHashCodes(int left, int right)
440         {
441             return ((left << 5) + left) ^ right;
442         }
443
444         private static int CombineHashCodes(int h1, int h2, int h3)
445         {
446             return CombineHashCodes(CombineHashCodes(h1, h2), h3);
447         }
448
449     }
450 }