// 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.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using EditorBrowsableAttribute = System.ComponentModel.EditorBrowsableAttribute; using EditorBrowsableState = System.ComponentModel.EditorBrowsableState; using Internal.Runtime.CompilerServices; #pragma warning disable 0809 //warning CS0809: Obsolete member 'Span.Equals(object)' overrides non-obsolete member 'object.Equals(object)' #if BIT64 using nuint = System.UInt64; #else using nuint = System.UInt32; #endif namespace System { /// /// ReadOnlySpan represents a contiguous region of arbitrary memory. Unlike arrays, it can point to either managed /// or native memory, or to memory allocated on the stack. It is type- and memory-safe. /// [NonVersionable] public readonly ref partial struct ReadOnlySpan { /// A byref or a native ptr. internal readonly ByReference _pointer; /// The number of elements this ReadOnlySpan contains. #if PROJECTN [Bound] #endif private readonly int _length; /// /// Creates a new read-only span over the entirety of the target array. /// /// The target array. /// Returns default when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpan(T[]? array) { if (array == null) { this = default; return; // returns default } _pointer = new ByReference(ref Unsafe.As(ref array.GetRawSzArrayData())); _length = array.Length; } /// /// Creates a new read-only span 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 read-only span. /// The number of items in the read-only span. /// Returns default when is null. /// /// Thrown when the specified or end index is not in the range (<0 or >Length). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpan(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 _pointer = new ByReference(ref Unsafe.Add(ref Unsafe.As(ref array.GetRawSzArrayData()), start)); _length = length; } /// /// Creates a new read-only span over the target unmanaged buffer. Clearly this /// is quite dangerous, because we are creating arbitrarily typed T's /// out of a void*-typed block of memory. And the length is not checked. /// But if this creation is correct, then all subsequent uses are correct. /// /// An unmanaged pointer to memory. /// The number of elements the memory contains. /// /// Thrown when is reference type or contains pointers and hence cannot be stored in unmanaged memory. /// /// /// Thrown when the specified is negative. /// [CLSCompliant(false)] [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe ReadOnlySpan(void* pointer, int length) { if (RuntimeHelpers.IsReferenceOrContainsReferences()) ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T)); if (length < 0) ThrowHelper.ThrowArgumentOutOfRangeException(); _pointer = new ByReference(ref Unsafe.As(ref *(byte*)pointer)); _length = length; } // Constructor for internal use only. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ReadOnlySpan(ref T ptr, int length) { Debug.Assert(length >= 0); _pointer = new ByReference(ref ptr); _length = length; } /// /// Returns the specified element of the read-only span. /// /// /// /// /// Thrown when index less than 0 or index greater than or equal to Length /// public ref readonly T this[int index] { #if PROJECTN [BoundsChecking] get { return ref Unsafe.Add(ref _pointer.Value, index); } #else [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] [NonVersionable] get { if ((uint)index >= (uint)_length) ThrowHelper.ThrowIndexOutOfRangeException(); return ref Unsafe.Add(ref _pointer.Value, index); } #endif } /// /// Returns a reference to the 0th element of the Span. If the Span is empty, returns null reference. /// It can be used for pinning and is required to support the use of span within a fixed statement. /// [EditorBrowsable(EditorBrowsableState.Never)] public unsafe ref readonly T GetPinnableReference() { // Ensure that the native code has just one forward branch that is predicted-not-taken. ref T ret = ref Unsafe.AsRef(null); if (_length != 0) ret = ref _pointer.Value; return ref ret; } /// /// Copies the contents of this read-only span into destination span. If the source /// and destinations overlap, this method behaves as if the original values in /// a temporary location before the destination is overwritten. /// /// The span to copy items into. /// /// Thrown when the destination Span is shorter than the source Span. /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void CopyTo(Span destination) { // Using "if (!TryCopyTo(...))" results in two branches: one for the length // check, and one for the result of TryCopyTo. Since these checks are equivalent, // we can optimize by performing the check once ourselves then calling Memmove directly. if ((uint)_length <= (uint)destination.Length) { Buffer.Memmove(ref destination._pointer.Value, ref _pointer.Value, (nuint)_length); } else { ThrowHelper.ThrowArgumentException_DestinationTooShort(); } } /// /// Copies the contents of this read-only span into destination span. If the source /// and destinations overlap, this method behaves as if the original values in /// a temporary location before the destination is overwritten. /// /// If the destination span is shorter than the source span, this method /// return false and no data is written to the destination. /// The span to copy items into. public bool TryCopyTo(Span destination) { bool retVal = false; if ((uint)_length <= (uint)destination.Length) { Buffer.Memmove(ref destination._pointer.Value, ref _pointer.Value, (nuint)_length); retVal = true; } return retVal; } /// /// Returns true if left and right point at the same memory and have the same length. Note that /// this does *not* check to see if the *contents* are equal. /// public static bool operator ==(ReadOnlySpan left, ReadOnlySpan right) { return left._length == right._length && Unsafe.AreSame(ref left._pointer.Value, ref right._pointer.Value); } /// /// For , returns a new instance of string that represents the characters pointed to by the span. /// Otherwise, returns a with the name of the type and the number of elements. /// public override string ToString() { if (typeof(T) == typeof(char)) { return new string(new ReadOnlySpan(ref Unsafe.As(ref _pointer.Value), _length)); } #if FEATURE_UTF8STRING else if (typeof(T) == typeof(Char8)) { // TODO_UTF8STRING: Call into optimized transcoding routine when it's available. return Encoding.UTF8.GetString(new ReadOnlySpan(ref Unsafe.As(ref _pointer.Value), _length)); } #endif // FEATURE_UTF8STRING return string.Format("System.ReadOnlySpan<{0}>[{1}]", typeof(T).Name, _length); } /// /// Forms a slice out of the given read-only span, 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 ReadOnlySpan Slice(int start) { if ((uint)start > (uint)_length) ThrowHelper.ThrowArgumentOutOfRangeException(); return new ReadOnlySpan(ref Unsafe.Add(ref _pointer.Value, start), _length - start); } /// /// Forms a slice out of the given read-only span, 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 ReadOnlySpan 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(); #else if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start)) ThrowHelper.ThrowArgumentOutOfRangeException(); #endif return new ReadOnlySpan(ref Unsafe.Add(ref _pointer.Value, start), length); } /// /// Copies the contents of this read-only span 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() { if (_length == 0) return Array.Empty(); var destination = new T[_length]; Buffer.Memmove(ref Unsafe.As(ref destination.GetRawSzArrayData()), ref _pointer.Value, (nuint)_length); return destination; } } }