// 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;
using Internal.Runtime.CompilerServices;
namespace System
{
///
/// Represents a contiguous region of memory, similar to .
/// Unlike , it is not a byref-like type.
///
[DebuggerTypeProxy(typeof(MemoryDebugView<>))]
[DebuggerDisplay("{ToString(),raw}")]
public readonly struct ReadOnlyMemory
{
// 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 a pre-pinned array.
// (_index < 0) => _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.
private readonly object _object;
private readonly int _index;
private readonly int _length;
internal 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 ReadOnlyMemory(T[] array)
{
if (array == null)
{
this = default;
return; // returns default
}
_object = array;
_index = 0;
_length = array.Length;
}
///
/// 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 ReadOnlyMemory(T[] array, int start, int length)
{
if (array == null)
{
if (start != 0 || length != 0)
ThrowHelper.ThrowArgumentOutOfRangeException();
this = default;
return; // returns default
}
#if BIT64
// See comment in Span.Slice for how this works.
if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)array.Length)
ThrowHelper.ThrowArgumentOutOfRangeException();
#else
if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start))
ThrowHelper.ThrowArgumentOutOfRangeException();
#endif
_object = array;
_index = start;
_length = length;
}
/// Creates a new memory over the existing object, start, and length. No validation is performed.
/// The target object.
/// The index at which to begin the memory.
/// The number of items in the memory.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal ReadOnlyMemory(object obj, int start, int length)
{
// No validation performed in release builds; caller must provide any necessary validation.
// 'obj is T[]' below also handles things like int[] <-> uint[] being convertible
Debug.Assert((obj == null) || (typeof(T) == typeof(char) && obj is string) || (obj is T[]) || (obj is MemoryManager));
_object = obj;
_index = start;
_length = length;
}
///
/// Defines an implicit conversion of an array to a
///
public static implicit operator ReadOnlyMemory(T[] array) => new ReadOnlyMemory(array);
///
/// Defines an implicit conversion of a to a
///
public static implicit operator ReadOnlyMemory(ArraySegment segment) => new ReadOnlyMemory(segment.Array, segment.Offset, segment.Count);
///
/// Returns an empty
///
public static ReadOnlyMemory Empty => default;
///
/// The number of items in the memory.
///
public int Length => _length;
///
/// Returns true if Length is 0.
///
public bool IsEmpty => _length == 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) : Span.ToString();
}
return string.Format("System.ReadOnlyMemory<{0}>[{1}]", typeof(T).Name, _length);
}
///
/// 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 ReadOnlyMemory Slice(int start)
{
if ((uint)start > (uint)_length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
}
// It is expected for _index + start to be negative if the memory is already pre-pinned.
return new ReadOnlyMemory(_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 ReadOnlyMemory Slice(int start, int length)
{
#if BIT64
// See comment in Span.Slice for how this works.
if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)_length)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
#else
if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start))
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
#endif
// It is expected for _index + start to be negative if the memory is already pre-pinned.
return new ReadOnlyMemory(_object, _index + start, length);
}
///
/// Returns a span from the memory.
///
public unsafe ReadOnlySpan Span
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
ref T refToReturn = ref Unsafe.AsRef(null);
int lengthOfUnderlyingSpan = 0;
// Copy this field into a local so that it can't change out from under us mid-operation.
object tmpObject = _object;
if (tmpObject != null)
{
if (typeof(T) == typeof(char) && tmpObject.GetType() == typeof(string))
{
// Special-case string since it's the most common for ROM.
refToReturn = ref Unsafe.As(ref Unsafe.As(tmpObject).GetRawStringData());
lengthOfUnderlyingSpan = Unsafe.As(tmpObject).Length;
}
else if (RuntimeHelpers.ObjectHasComponentSize(tmpObject))
{
// We know the object is not null, it's not a string, and it is variable-length. The only
// remaining option is for it to be a T[] (or a U[] which is blittable to T[], like int[]
// and uint[]). Otherwise somebody used private reflection to set this field, and we're not
// too worried about type safety violations at this point.
// 'tmpObject is T[]' below also handles things like int[] <-> uint[] being convertible
Debug.Assert(tmpObject is T[]);
refToReturn = ref Unsafe.As(ref Unsafe.As(tmpObject).GetRawSzArrayData());
lengthOfUnderlyingSpan = Unsafe.As(tmpObject).Length;
}
else
{
// We know the object is not null, and it's not variable-length, so it must be a MemoryManager.
// Otherwise somebody used private reflection to set this field, and we're not too worried about
// type safety violations at that point. Note that it can't be a MemoryManager, even if U and
// T are blittable (e.g., MemoryManager to MemoryManager), since there exists no
// constructor or other public API which would allow such a conversion.
Debug.Assert(tmpObject is MemoryManager);
Span memoryManagerSpan = Unsafe.As>(tmpObject).GetSpan();
refToReturn = ref MemoryMarshal.GetReference(memoryManagerSpan);
lengthOfUnderlyingSpan = memoryManagerSpan.Length;
}
// If the Memory or ReadOnlyMemory instance is torn, this property getter has undefined behavior.
// We try to detect this condition and throw an exception, but it's possible that a torn struct might
// appear to us to be valid, and we'll return an undesired span. Such a span is always guaranteed at
// least to be in-bounds when compared with the original Memory instance, so using the span won't
// AV the process.
int desiredStartIndex = _index & RemoveFlagsBitMask;
int desiredLength = _length;
#if BIT64
// See comment in Span.Slice for how this works.
if ((ulong)(uint)desiredStartIndex + (ulong)(uint)desiredLength > (ulong)(uint)lengthOfUnderlyingSpan)
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
#else
if ((uint)desiredStartIndex > (uint)lengthOfUnderlyingSpan || (uint)desiredLength > (uint)(lengthOfUnderlyingSpan - desiredStartIndex))
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
#endif
refToReturn = ref Unsafe.Add(ref refToReturn, desiredStartIndex);
lengthOfUnderlyingSpan = desiredLength;
}
return new ReadOnlySpan(ref refToReturn, lengthOfUnderlyingSpan);
}
}
///
/// Copies the contents of the read-only 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 readonly-only 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()
{
// It's possible that the below logic could result in an AV if the struct
// is torn. This is ok since the caller is expecting to use raw pointers,
// and we're not required to keep this as safe as the other Span-based APIs.
object tmpObject = _object;
if (tmpObject != null)
{
if (typeof(T) == typeof(char) && tmpObject is string s)
{
GCHandle handle = GCHandle.Alloc(tmpObject, GCHandleType.Pinned);
ref char stringData = ref Unsafe.Add(ref s.GetRawStringData(), _index);
return new MemoryHandle(Unsafe.AsPointer(ref stringData), handle);
}
else if (RuntimeHelpers.ObjectHasComponentSize(tmpObject))
{
// 'tmpObject is T[]' below also handles things like int[] <-> uint[] being convertible
Debug.Assert(tmpObject is T[]);
// Array is already pre-pinned
if (_index < 0)
{
void* pointer = Unsafe.Add(Unsafe.AsPointer(ref Unsafe.As(tmpObject).GetRawSzArrayData()), _index & RemoveFlagsBitMask);
return new MemoryHandle(pointer);
}
else
{
GCHandle handle = GCHandle.Alloc(tmpObject, GCHandleType.Pinned);
void* pointer = Unsafe.Add(Unsafe.AsPointer(ref Unsafe.As(tmpObject).GetRawSzArrayData()), _index);
return new MemoryHandle(pointer, handle);
}
}
else
{
Debug.Assert(tmpObject is MemoryManager);
return Unsafe.As>(tmpObject).Pin(_index);
}
}
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.
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object obj)
{
if (obj is ReadOnlyMemory readOnlyMemory)
{
return Equals(readOnlyMemory);
}
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(ReadOnlyMemory other)
{
return
_object == other._object &&
_index == other._index &&
_length == other._length;
}
/// Returns the hash code for this
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode()
{
// We use RuntimeHelpers.GetHashCode instead of Object.GetHashCode because the hash
// code is based on object identity and referential equality, not deep equality (as common with string).
return (_object != null) ? HashCode.Combine(RuntimeHelpers.GetHashCode(_object), _index, _length) : 0;
}
/// Gets the state of the memory as individual fields.
/// The offset.
/// The count.
/// The object.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal object GetObjectStartLength(out int start, out int length)
{
start = _index;
length = _length;
return _object;
}
}
}