// 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 EditorBrowsableState = System.ComponentModel.EditorBrowsableState; using EditorBrowsableAttribute = System.ComponentModel.EditorBrowsableAttribute; #pragma warning disable 0809 //warning CS0809: Obsolete member 'Span.Equals(object)' overrides non-obsolete member 'object.Equals(object)' namespace System { /// /// Span 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. /// public struct Span { /// A byref or a native ptr. private readonly ByReference _pointer; /// The number of elements this Span contains. private readonly int _length; /// /// Creates a new span over the entirety of the target array. /// /// The target array. /// Thrown when is a null /// reference (Nothing in Visual Basic). /// Thrown when is covariant and array's type is not exactly T[]. [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span(T[] array) { if (array == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); if (default(T) == null && array.GetType() != typeof(T[])) ThrowHelper.ThrowArrayTypeMismatchException(); _pointer = new ByReference(ref JitHelpers.GetArrayData(array)); _length = array.Length; } /// /// Creates a new span over the portion of the target array beginning /// at 'start' index and covering the remainder of the array. /// /// The target array. /// The index at which to begin the span. /// Thrown when is a null /// reference (Nothing in Visual Basic). /// Thrown when is covariant and array's type is not exactly T[]. /// /// Thrown when the specified is not in the range (<0 or >=Length). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span(T[] array, int start) { if (array == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); if (default(T) == null && array.GetType() != typeof(T[])) ThrowHelper.ThrowArrayTypeMismatchException(); if ((uint)start > (uint)array.Length) ThrowHelper.ThrowArgumentOutOfRangeException(); _pointer = new ByReference(ref Unsafe.Add(ref JitHelpers.GetArrayData(array), start)); _length = array.Length - start; } /// /// Creates a new 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 span. /// The number of items in the span. /// Thrown when is a null /// reference (Nothing in Visual Basic). /// 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 Span(T[] array, int start, int length) { if (array == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); if (default(T) == null && array.GetType() != typeof(T[])) ThrowHelper.ThrowArrayTypeMismatchException(); if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start)) ThrowHelper.ThrowArgumentOutOfRangeException(); _pointer = new ByReference(ref Unsafe.Add(ref JitHelpers.GetArrayData(array), start)); _length = length; } /// /// Creates a new 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 Span(void* pointer, int length) { if (JitHelpers.ContainsReferences()) ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T)); if (length < 0) ThrowHelper.ThrowArgumentOutOfRangeException(); _pointer = new ByReference(ref Unsafe.As(ref *(byte*)pointer)); _length = length; } /// /// Create a new span over a portion of a regular managed object. This can be useful /// if part of a managed object represents a "fixed array." This is dangerous because /// "length" is not checked, nor is the fact that "rawPointer" actually lies within the object. /// /// The managed object that contains the data to span over. /// A reference to data within that object. /// The number of elements the memory contains. /// /// Thrown when the specified object is null. /// /// /// Thrown when the specified is negative. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span DangerousCreate(object obj, ref T objectData, int length) { if (obj == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.obj); if (length < 0) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length); return new Span(ref objectData, length); } // Constructor for internal use only. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Span(ref T ptr, int length) { Debug.Assert(length >= 0); _pointer = new ByReference(ref ptr); _length = length; } /// /// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to the location where the 0th element /// would have been stored. Such a reference can be used for pinning but must never be dereferenced. /// public ref T DangerousGetPinnableReference() { return ref _pointer.Value; } /// /// The number of items in the span. /// public int Length => _length; /// /// Returns true if Length is 0. /// public bool IsEmpty => _length == 0; /// /// Returns a reference to specified element of the Span. /// /// /// /// /// Thrown when index less than 0 or index greater than or equal to Length /// // TODO: https://github.com/dotnet/corefx/issues/13681 // Until we get over the hurdle of C# 7 tooling, this indexer will return "T" and have a setter rather than a "ref T". (The doc comments // continue to reflect the original intent of returning "ref T") public T this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { if ((uint)index >= (uint)_length) ThrowHelper.ThrowIndexOutOfRangeException(); return Unsafe.Add(ref _pointer.Value, index); } [MethodImpl(MethodImplOptions.AggressiveInlining)] set { if ((uint)index >= (uint)_length) ThrowHelper.ThrowIndexOutOfRangeException(); Unsafe.Add(ref _pointer.Value, index) = value; } } /// /// Returns a reference to specified element of the Span. /// /// /// /// /// Thrown when index less than 0 or index greater than or equal to Length /// // TODO: https://github.com/dotnet/corefx/issues/13681 // Until we get over the hurdle of C# 7 tooling, this temporary method will simulate the intended "ref T" indexer for those // who need bypass the workaround for performance. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T GetItem(int index) { if ((uint)index >= ((uint)_length)) ThrowHelper.ThrowIndexOutOfRangeException(); return ref Unsafe.Add(ref _pointer.Value, index); } /// /// Clears the contents of this span. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { // TODO: Optimize - https://github.com/dotnet/coreclr/issues/9161 for (int i = 0; i < _length; i++) { this[i] = default(T); } } /// /// Fills the contents of this span with the given value. /// public void Fill(T value) { int length = _length; if (length == 0) return; if (Unsafe.SizeOf() == 1) { byte fill = Unsafe.As(ref value); ref byte r = ref Unsafe.As(ref _pointer.Value); Unsafe.InitBlockUnaligned(ref r, fill, (uint)length); } else { ref T r = ref DangerousGetPinnableReference(); // TODO: Create block fill for value types of power of two sizes e.g. 2,4,8,16 // Simple loop unrolling int i = 0; for (; i < (length & ~7); i += 8) { Unsafe.Add(ref r, i + 0) = value; Unsafe.Add(ref r, i + 1) = value; Unsafe.Add(ref r, i + 2) = value; Unsafe.Add(ref r, i + 3) = value; Unsafe.Add(ref r, i + 4) = value; Unsafe.Add(ref r, i + 5) = value; Unsafe.Add(ref r, i + 6) = value; Unsafe.Add(ref r, i + 7) = value; } if (i < (length & ~3)) { Unsafe.Add(ref r, i + 0) = value; Unsafe.Add(ref r, i + 1) = value; Unsafe.Add(ref r, i + 2) = value; Unsafe.Add(ref r, i + 3) = value; i += 4; } for (; i < length; i++) { Unsafe.Add(ref r, i) = value; } } } /// /// Copies the contents of this 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. /// public void CopyTo(Span destination) { if (!TryCopyTo(destination)) ThrowHelper.ThrowArgumentException_DestinationTooShort(); } /// /// Copies the contents of this 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. /// If the destination span is shorter than the source span, this method /// return false and no data is written to the destination. public bool TryCopyTo(Span destination) { if ((uint)_length > (uint)destination.Length) return false; SpanHelper.CopyTo(ref destination._pointer.Value, ref _pointer.Value, _length); return true; } /// /// 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 ==(Span left, Span right) { return left._length == right._length && Unsafe.AreSame(ref left._pointer.Value, ref right._pointer.Value); } /// /// Returns false 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 !=(Span left, Span right) => !(left == right); /// /// This method is not supported as spans cannot be boxed. To compare two spans, use operator==. /// /// Always thrown by this method. /// /// [Obsolete("Equals() on Span will always throw an exception. Use == instead.")] [EditorBrowsable(EditorBrowsableState.Never)] public override bool Equals(object obj) { ThrowHelper.ThrowNotSupportedException_CannotCallEqualsOnSpan(); // Prevent compiler error CS0161: 'Span.Equals(object)': not all code paths return a value return default(bool); } /// /// This method is not supported as spans cannot be boxed. /// /// Always thrown by this method. /// /// [Obsolete("GetHashCode() on Span will always throw an exception.")] [EditorBrowsable(EditorBrowsableState.Never)] public override int GetHashCode() { ThrowHelper.ThrowNotSupportedException_CannotCallGetHashCodeOnSpan(); // Prevent compiler error CS0161: 'Span.GetHashCode()': not all code paths return a value return default(int); } /// /// Defines an implicit conversion of an array to a /// public static implicit operator Span(T[] array) => new Span(array); /// /// Defines an implicit conversion of a to a /// public static implicit operator Span(ArraySegment arraySegment) => new Span(arraySegment.Array, arraySegment.Offset, arraySegment.Count); /// /// Defines an implicit conversion of a to a /// public static implicit operator ReadOnlySpan(Span span) => new ReadOnlySpan(ref span._pointer.Value, span._length); /// /// Forms a slice out of the given 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 Span Slice(int start) { if ((uint)start > (uint)_length) ThrowHelper.ThrowArgumentOutOfRangeException(); return new Span(ref Unsafe.Add(ref _pointer.Value, start), _length - start); } /// /// Forms a slice out of the given 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 Span Slice(int start, int length) { if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start)) ThrowHelper.ThrowArgumentOutOfRangeException(); return new Span(ref Unsafe.Add(ref _pointer.Value, start), length); } /// /// Copies the contents of this 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. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public T[] ToArray() { if (_length == 0) return Array.Empty(); var destination = new T[_length]; SpanHelper.CopyTo(ref JitHelpers.GetArrayData(destination), ref _pointer.Value, _length); return destination; } // /// Returns an empty /// public static Span Empty => default(Span); } public static class Span { /// /// Casts a Span of one primitive type to Span of bytes. /// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety. /// /// The source slice, of type . /// /// Thrown when contains pointers. /// public static Span AsBytes(this Span source) where T : struct { if (JitHelpers.ContainsReferences()) ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T)); return new Span( ref Unsafe.As(ref source.DangerousGetPinnableReference()), checked(source.Length * Unsafe.SizeOf())); } /// /// Casts a ReadOnlySpan of one primitive type to ReadOnlySpan of bytes. /// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety. /// /// The source slice, of type . /// /// Thrown when contains pointers. /// public static ReadOnlySpan AsBytes(this ReadOnlySpan source) where T : struct { if (JitHelpers.ContainsReferences()) ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T)); return new ReadOnlySpan( ref Unsafe.As(ref source.DangerousGetPinnableReference()), checked(source.Length * Unsafe.SizeOf())); } /// /// Casts a Span of one primitive type to another primitive type . /// These types may not contain pointers or references. This is checked at runtime in order to preserve type safety. /// /// /// Supported only for platforms that support misaligned memory access. /// /// The source slice, of type . /// /// Thrown when or contains pointers. /// public static Span NonPortableCast(this Span source) where TFrom : struct where TTo : struct { if (JitHelpers.ContainsReferences()) ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(TFrom)); if (JitHelpers.ContainsReferences()) ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(TTo)); return new Span( ref Unsafe.As(ref source.DangerousGetPinnableReference()), checked((int)((long)source.Length * Unsafe.SizeOf() / Unsafe.SizeOf()))); } /// /// Casts a ReadOnlySpan of one primitive type to another primitive type . /// These types may not contain pointers or references. This is checked at runtime in order to preserve type safety. /// /// /// Supported only for platforms that support misaligned memory access. /// /// The source slice, of type . /// /// Thrown when or contains pointers. /// public static ReadOnlySpan NonPortableCast(this ReadOnlySpan source) where TFrom : struct where TTo : struct { if (JitHelpers.ContainsReferences()) ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(TFrom)); if (JitHelpers.ContainsReferences()) ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(TTo)); return new ReadOnlySpan( ref Unsafe.As(ref source.DangerousGetPinnableReference()), checked((int)((long)source.Length * Unsafe.SizeOf() / Unsafe.SizeOf()))); } /// /// Creates a new readonly span over the portion of the target string. /// /// The target string. /// Thrown when is a null /// reference (Nothing in Visual Basic). [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ReadOnlySpan Slice(this string text) { if (text == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text); return new ReadOnlySpan(ref text.GetFirstCharRef(), text.Length); } /// /// Creates a new readonly span over the portion of the target string, beginning at 'start'. /// /// The target string. /// The index at which to begin this slice. /// Thrown when is a null /// reference (Nothing in Visual Basic). /// /// Thrown when the specified index is not in range (<0 or >Length). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ReadOnlySpan Slice(this string text, int start) { if (text == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text); if ((uint)start > (uint)text.Length) ThrowHelper.ThrowArgumentOutOfRangeException(); return new ReadOnlySpan(ref Unsafe.Add(ref text.GetFirstCharRef(), start), text.Length - start); } /// /// Creates a new readonly span over the portion of the target string, beginning at , of given . /// /// The target string. /// The index at which to begin this slice. /// The number of items in the span. /// Thrown when is a null /// reference (Nothing in Visual Basic). /// /// Thrown when the specified or end index is not in range (<0 or >&eq;Length). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ReadOnlySpan Slice(this string text, int start, int length) { if (text == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text); if ((uint)start > (uint)text.Length || (uint)length > (uint)(text.Length - start)) ThrowHelper.ThrowArgumentOutOfRangeException(); return new ReadOnlySpan(ref Unsafe.Add(ref text.GetFirstCharRef(), start), length); } } internal static class SpanHelper { internal static unsafe void CopyTo(ref T destination, ref T source, int elementsCount) { if (elementsCount == 0) return; if (Unsafe.AreSame(ref destination, ref source)) return; if (!JitHelpers.ContainsReferences()) { fixed (byte* pDestination = &Unsafe.As(ref destination)) { fixed (byte* pSource = &Unsafe.As(ref source)) { #if BIT64 Buffer.Memmove(pDestination, pSource, (ulong)elementsCount * (ulong)Unsafe.SizeOf()); #else Buffer.Memmove(pDestination, pSource, (uint)elementsCount * (uint)Unsafe.SizeOf()); #endif } } } else { if (JitHelpers.ByRefLessThan(ref destination, ref source)) // copy forward { for (int i = 0; i < elementsCount; i++) Unsafe.Add(ref destination, i) = Unsafe.Add(ref source, i); } else // copy backward to avoid overlapping issues { for (int i = elementsCount - 1; i >= 0; i--) Unsafe.Add(ref destination, i) = Unsafe.Add(ref source, i); } } } } }