// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using EditorBrowsableAttribute = System.ComponentModel.EditorBrowsableAttribute; using EditorBrowsableState = System.ComponentModel.EditorBrowsableState; #if !FEATURE_PORTABLE_SPAN using Internal.Runtime.CompilerServices; #endif // FEATURE_PORTABLE_SPAN namespace System { /// /// Memory represents a contiguous region of arbitrary memory similar to . /// Unlike , it is not a byref-like type. /// [DebuggerDisplay("{DebuggerDisplay,nq}")] [DebuggerTypeProxy(typeof(MemoryDebugView<>))] public readonly struct Memory { // NOTE: With the current implementation, Memory and ReadOnlyMemory must have the same layout, // as code uses Unsafe.As to cast between them. // The highest order bit of _index is used to discern whether _object is an array/string or an owned memory // if (_index >> 31) == 1, object _object is an OwnedMemory // else, object _object is a T[] or a string. It can only be a string if the Memory was created by // using unsafe / marshaling code to reinterpret a ReadOnlyMemory wrapped around a string as // a Memory. private readonly object _object; private readonly int _index; private readonly int _length; private const int RemoveOwnedFlagBitMask = 0x7FFFFFFF; /// /// Creates a new memory over the entirety of the target array. /// /// The target array. /// Returns default when is null. /// Thrown when is covariant and array's type is not exactly T[]. [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory(T[] array) { if (array == null) { this = default; return; // returns default } if (default(T) == null && array.GetType() != typeof(T[])) ThrowHelper.ThrowArrayTypeMismatchException(); _object = array; _index = 0; _length = array.Length; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Memory(T[] array, int start) { if (array == null) { if (start != 0) ThrowHelper.ThrowArgumentOutOfRangeException(); this = default; return; // returns default } if (default(T) == null && array.GetType() != typeof(T[])) ThrowHelper.ThrowArrayTypeMismatchException(); if ((uint)start > (uint)array.Length) ThrowHelper.ThrowArgumentOutOfRangeException(); _object = array; _index = start; _length = array.Length - start; } /// /// Creates a new memory over the portion of the target array beginning /// at 'start' index and ending at 'end' index (exclusive). /// /// The target array. /// The index at which to begin the memory. /// The number of items in the memory. /// Returns default when is null. /// Thrown when is covariant and array's type is not exactly T[]. /// /// Thrown when the specified or end index is not in the range (<0 or >=Length). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory(T[] array, int start, int length) { if (array == null) { if (start != 0 || length != 0) ThrowHelper.ThrowArgumentOutOfRangeException(); this = default; return; // returns default } if (default(T) == null && array.GetType() != typeof(T[])) ThrowHelper.ThrowArrayTypeMismatchException(); if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start)) ThrowHelper.ThrowArgumentOutOfRangeException(); _object = array; _index = start; _length = length; } // Constructor for internal use only. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Memory(OwnedMemory owner, int index, int length) { // No validation performed; caller must provide any necessary validation. _object = owner; _index = index | (1 << 31); // Before using _index, check if _index < 0, then 'and' it with RemoveOwnedFlagBitMask _length = length; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private Memory(object obj, int index, int length) { // No validation performed; caller must provide any necessary validation. _object = obj; _index = index; _length = length; } /// /// Defines an implicit conversion of an array to a /// public static implicit operator Memory(T[] array) => new Memory(array); /// /// Defines an implicit conversion of a to a /// public static implicit operator Memory(ArraySegment arraySegment) => new Memory(arraySegment.Array, arraySegment.Offset, arraySegment.Count); /// /// Defines an implicit conversion of a to a /// public static implicit operator ReadOnlyMemory(Memory memory) => Unsafe.As, ReadOnlyMemory>(ref memory); //Debugger Display = {T[length]} private string DebuggerDisplay => string.Format("{{{0}[{1}]}}", typeof(T).Name, _length); /// /// Returns an empty /// public static Memory Empty => default; /// /// The number of items in the memory. /// public int Length => _length; /// /// Returns true if Length is 0. /// public bool IsEmpty => _length == 0; /// /// Forms a slice out of the given memory, beginning at 'start'. /// /// The index at which to begin this slice. /// /// Thrown when the specified index is not in range (<0 or >=Length). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory Slice(int start) { if ((uint)start > (uint)_length) { ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); } return new Memory(_object, _index + start, _length - start); } /// /// Forms a slice out of the given memory, beginning at 'start', of given length /// /// The index at which to begin this slice. /// The desired length for the slice (exclusive). /// /// Thrown when the specified or end index is not in range (<0 or >=Length). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory Slice(int start, int length) { if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start)) { ThrowHelper.ThrowArgumentOutOfRangeException(); } return new Memory(_object, _index + start, length); } /// /// Returns a span from the memory. /// public Span Span { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { if (_index < 0) { return ((OwnedMemory)_object).Span.Slice(_index & RemoveOwnedFlagBitMask, _length); } else if (typeof(T) == typeof(char) && _object is string s) { // This is dangerous, returning a writable span for a string that should be immutable. // However, we need to handle the case where a ReadOnlyMemory was created from a string // and then cast to a Memory. Such a cast can only be done with unsafe or marshaling code, // in which case that's the dangerous operation performed by the dev, and we're just following // suit here to make it work as best as possible. #if FEATURE_PORTABLE_SPAN return new Span(Unsafe.As>(s), MemoryExtensions.StringAdjustment, s.Length).Slice(_index, _length); #else return new Span(ref Unsafe.As(ref s.GetRawStringData()), s.Length).Slice(_index, _length); #endif // FEATURE_PORTABLE_SPAN } else if (_object != null) { return new Span((T[])_object, _index, _length); } else { return default; } } } /// /// Copies the contents of the memory into the destination. If the source /// and destination overlap, this method behaves as if the original values are in /// a temporary location before the destination is overwritten. /// /// The Memory to copy items into. /// /// Thrown when the destination is shorter than the source. /// /// public void CopyTo(Memory destination) => Span.CopyTo(destination.Span); /// /// Copies the contents of the memory into the destination. If the source /// and destination overlap, this method behaves as if the original values are in /// a temporary location before the destination is overwritten. /// /// If the destination is shorter than the source, this method /// return false and no data is written to the destination. /// /// The span to copy items into. public bool TryCopyTo(Memory destination) => Span.TryCopyTo(destination.Span); /// /// Returns a handle for the array. /// If pin is true, the GC will not move the array and hence its address can be taken /// public unsafe MemoryHandle Retain(bool pin = false) { MemoryHandle memoryHandle = default; if (pin) { if (_index < 0) { memoryHandle = ((OwnedMemory)_object).Pin((_index & RemoveOwnedFlagBitMask) * Unsafe.SizeOf()); } else if (typeof(T) == typeof(char) && _object is string s) { // This case can only happen if a ReadOnlyMemory was created around a string // and then that was cast to a Memory using unsafe / marshaling code. This needs // to work, however, so that code that uses a single Memory field to store either // a readable ReadOnlyMemory or a writable Memory can still be pinned and // used for interop purposes. GCHandle handle = GCHandle.Alloc(s, GCHandleType.Pinned); #if FEATURE_PORTABLE_SPAN void* pointer = Unsafe.Add((void*)handle.AddrOfPinnedObject(), _index); #else void* pointer = Unsafe.Add(Unsafe.AsPointer(ref s.GetRawStringData()), _index); #endif // FEATURE_PORTABLE_SPAN memoryHandle = new MemoryHandle(null, pointer, handle); } else if (_object is T[] array) { var handle = GCHandle.Alloc(array, GCHandleType.Pinned); #if FEATURE_PORTABLE_SPAN void* pointer = Unsafe.Add((void*)handle.AddrOfPinnedObject(), _index); #else void* pointer = Unsafe.Add(Unsafe.AsPointer(ref array.GetRawSzArrayData()), _index); #endif // FEATURE_PORTABLE_SPAN memoryHandle = new MemoryHandle(null, pointer, handle); } } else { if (_index < 0) { ((OwnedMemory)_object).Retain(); memoryHandle = new MemoryHandle((OwnedMemory)_object); } } return memoryHandle; } /// /// Copies the contents from the memory into a new array. This heap /// allocates, so should generally be avoided, however it is sometimes /// necessary to bridge the gap with APIs written in terms of arrays. /// public T[] ToArray() => Span.ToArray(); /// /// Determines whether the specified object is equal to the current object. /// Returns true if the object is Memory or ReadOnlyMemory and if both objects point to the same array and have the same length. /// [EditorBrowsable(EditorBrowsableState.Never)] public override bool Equals(object obj) { if (obj is ReadOnlyMemory) { return ((ReadOnlyMemory)obj).Equals(this); } else if (obj is Memory memory) { return Equals(memory); } else { return false; } } /// /// Returns true if the memory points to the same array and has the same length. Note that /// this does *not* check to see if the *contents* are equal. /// public bool Equals(Memory other) { return _object == other._object && _index == other._index && _length == other._length; } /// /// Serves as the default hash function. /// [EditorBrowsable(EditorBrowsableState.Never)] public override int GetHashCode() { return _object != null ? CombineHashCodes(_object.GetHashCode(), _index.GetHashCode(), _length.GetHashCode()) : 0; } private static int CombineHashCodes(int left, int right) { return ((left << 5) + left) ^ right; } private static int CombineHashCodes(int h1, int h2, int h3) { return CombineHashCodes(CombineHashCodes(h1, h2), h3); } } }