// 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.
///
[DebuggerTypeProxy(typeof(MemoryDebugView<>))]
[DebuggerDisplay("{ToString(),raw}")]
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 MemoryManager
// else, object _object is a T[] or a string.
// if (_length >> 31) == 1, _object is a pre-pinned array, so Pin() will not allocate a new GCHandle
// else, Pin() needs to allocate a new GCHandle to pin the object.
// 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 RemoveFlagsBitMask = 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;
}
///
/// Creates a new memory from a memory manager that provides specific method implementations beginning
/// at 0 index and ending at 'end' index (exclusive).
///
/// The memory manager.
/// The number of items in the memory.
///
/// Thrown when the specified is negative.
///
/// For internal infrastructure only
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Memory(MemoryManager manager, int length)
{
Debug.Assert(manager != null);
if (length < 0)
ThrowHelper.ThrowArgumentOutOfRangeException();
_object = manager;
_index = (1 << 31); // Mark as MemoryManager type
// Before using _index, check if _index < 0, then 'and' it with RemoveFlagsBitMask
_length = length;
}
///
/// Creates a new memory from a memory manager that provides specific method implementations beginning
/// at 'start' index and ending at 'end' index (exclusive).
///
/// The memory manager.
/// The index at which to begin the memory.
/// The number of items in the memory.
///
/// Thrown when the specified or is negative.
///
/// For internal infrastructure only
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Memory(MemoryManager manager, int start, int length)
{
Debug.Assert(manager != null);
if (length < 0 || start < 0)
ThrowHelper.ThrowArgumentOutOfRangeException();
_object = manager;
_index = start | (1 << 31); // Mark as MemoryManager type
// Before using _index, check if _index < 0, then 'and' it with RemoveFlagsBitMask
_length = length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Memory(object obj, int start, int length)
{
// No validation performed; caller must provide any necessary validation.
_object = obj;
_index = start;
_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 segment) => new Memory(segment.Array, segment.Offset, segment.Count);
///
/// Defines an implicit conversion of a to a
///
public static implicit operator ReadOnlyMemory(Memory memory) =>
Unsafe.As, ReadOnlyMemory>(ref memory);
///
/// Returns an empty
///
public static Memory Empty => default;
///
/// The number of items in the memory.
///
public int Length => _length & RemoveFlagsBitMask;
///
/// Returns true if Length is 0.
///
public bool IsEmpty => (_length & RemoveFlagsBitMask) == 0;
///
/// For , returns a new instance of string that represents the characters pointed to by the memory.
/// Otherwise, returns a with the name of the type and the number of elements.
///
public override string ToString()
{
if (typeof(T) == typeof(char))
{
return (_object is string str) ? str.Substring(_index, _length & RemoveFlagsBitMask) : Span.ToString();
}
return string.Format("System.Memory<{0}>[{1}]", typeof(T).Name, _length & RemoveFlagsBitMask);
}
///
/// 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)
{
// Used to maintain the high-bit which indicates whether the Memory has been pre-pinned or not.
int capturedLength = _length;
int actualLength = capturedLength & RemoveFlagsBitMask;
if ((uint)start > (uint)actualLength)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
}
// It is expected for (capturedLength - start) to be negative if the memory is already pre-pinned.
return new Memory(_object, _index + start, capturedLength - 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)
{
// Used to maintain the high-bit which indicates whether the Memory has been pre-pinned or not.
int capturedLength = _length;
int actualLength = capturedLength & RemoveFlagsBitMask;
if ((uint)start > (uint)actualLength || (uint)length > (uint)(actualLength - start))
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
// Set the high-bit to match the this._length high bit (1 for pre-pinned, 0 for unpinned).
return new Memory(_object, _index + start, length | (capturedLength & ~RemoveFlagsBitMask));
}
///
/// Returns a span from the memory.
///
public Span Span
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
if (_index < 0)
{
Debug.Assert(_length >= 0);
Debug.Assert(_object != null);
return ((MemoryManager)_object).GetSpan().Slice(_index & RemoveFlagsBitMask, _length);
}
else if (typeof(T) == typeof(char) && _object is string s)
{
Debug.Assert(_length >= 0);
// 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 & RemoveFlagsBitMask);
}
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);
///
/// Creates a handle for the memory.
/// The GC will not move the memory until the returned
/// is disposed, enabling taking and using the memory's address.
///
/// An instance with nonprimitive (non-blittable) members cannot be pinned.
///
///
public unsafe MemoryHandle Pin()
{
if (_index < 0)
{
Debug.Assert(_object != null);
return ((MemoryManager)_object).Pin((_index & RemoveFlagsBitMask));
}
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
return new MemoryHandle(pointer, handle);
}
else if (_object is T[] array)
{
// Array is already pre-pinned
if (_length < 0)
{
#if FEATURE_PORTABLE_SPAN
void* pointer = Unsafe.Add(Unsafe.AsPointer(ref MemoryMarshal.GetReference(array)), _index);
#else
void* pointer = Unsafe.Add(Unsafe.AsPointer(ref array.GetRawSzArrayData()), _index);
#endif // FEATURE_PORTABLE_SPAN
return new MemoryHandle(pointer);
}
else
{
GCHandle 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
return new MemoryHandle(pointer, handle);
}
}
return default;
}
///
/// 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);
}
}
}