Index and Range updates (dotnet/coreclr#22331)
authorTarek Mahmoud Sayed <tarekms@microsoft.com>
Wed, 6 Feb 2019 23:15:46 +0000 (15:15 -0800)
committerGitHub <noreply@github.com>
Wed, 6 Feb 2019 23:15:46 +0000 (15:15 -0800)
* Index and Range updates

* Address @mikedn  feedback

* Address Feedback

* more feedback

* Use Deconstruct in Range.GetOffsetAndLength

* Rename GetArrayRange to GetSubArray

* Temporary disable the old Corefx Range tests

* Return back the TimeSpan test disabling

* Fix Range jit test

* Exclude the jit test

* revert the changes in the jit Range test

* Address Suggested Feedback

Commit migrated from https://github.com/dotnet/coreclr/commit/fe1185343cd02559824fcbfaf69967166978e8e5

15 files changed:
src/coreclr/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs
src/coreclr/tests/CoreFX/CoreFX.issues.json
src/coreclr/tests/issues.targets
src/coreclr/tests/src/JIT/Regression/JitBlue/GitHub_20958/GitHub_20958.cs
src/libraries/System.Private.CoreLib/src/System/Index.cs
src/libraries/System.Private.CoreLib/src/System/Memory.cs
src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Fast.cs
src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs
src/libraries/System.Private.CoreLib/src/System/Range.cs
src/libraries/System.Private.CoreLib/src/System/ReadOnlyMemory.cs
src/libraries/System.Private.CoreLib/src/System/ReadOnlySpan.Fast.cs
src/libraries/System.Private.CoreLib/src/System/Span.Fast.cs
src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs
src/libraries/System.Private.CoreLib/src/System/String.cs
src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs

index fe101e2..94a6379 100644 (file)
@@ -39,14 +39,14 @@ namespace System.Runtime.CompilerServices
         // GetObjectValue is intended to allow value classes to be manipulated as 'Object'
         // but have aliasing behavior of a value class.  The intent is that you would use
         // this function just before an assignment to a variable of type 'Object'.  If the
-        // value being assigned is a mutable value class, then a shallow copy is returned 
+        // value being assigned is a mutable value class, then a shallow copy is returned
         // (because value classes have copy semantics), but otherwise the object itself
-        // is returned.  
+        // is returned.
         //
         // Note: VB calls this method when they're about to assign to an Object
-        // or pass it as a parameter.  The goal is to make sure that boxed 
-        // value types work identical to unboxed value types - ie, they get 
-        // cloned when you pass them around, and are always passed by value.  
+        // or pass it as a parameter.  The goal is to make sure that boxed
+        // value types work identical to unboxed value types - ie, they get
+        // cloned when you pass them around, and are always passed by value.
         // Of course, reference types are not cloned.
         //
         [MethodImplAttribute(MethodImplOptions.InternalCall)]
@@ -57,8 +57,8 @@ namespace System.Runtime.CompilerServices
         // have at least been started by some thread.  In the absence of class constructor
         // deadlock conditions, the call is further guaranteed to have completed.
         //
-        // This call will generate an exception if the specified class constructor threw an 
-        // exception when it ran. 
+        // This call will generate an exception if the specified class constructor threw an
+        // exception when it ran.
 
         [MethodImplAttribute(MethodImplOptions.InternalCall)]
         private static extern void _RunClassConstructor(RuntimeType type);
@@ -73,8 +73,8 @@ namespace System.Runtime.CompilerServices
         // have at least been started by some thread.  In the absence of module constructor
         // deadlock conditions, the call is further guaranteed to have completed.
         //
-        // This call will generate an exception if the specified module constructor threw an 
-        // exception when it ran. 
+        // This call will generate an exception if the specified module constructor threw an
+        // exception when it ran.
 
         [MethodImplAttribute(MethodImplOptions.InternalCall)]
         private static extern void _RunModuleConstructor(System.Reflection.RuntimeModule module);
@@ -91,7 +91,7 @@ namespace System.Runtime.CompilerServices
         [MethodImplAttribute(MethodImplOptions.InternalCall)]
         private static extern unsafe void _PrepareMethod(IRuntimeMethodInfo method, IntPtr* pInstantiation, int cInstantiation);
 
-        public static void PrepareMethod(RuntimeMethodHandle method) 
+        public static void PrepareMethod(RuntimeMethodHandle method)
         {
             unsafe
             {
@@ -132,10 +132,10 @@ namespace System.Runtime.CompilerServices
             get
             {
                 // Number of bytes from the address pointed to by a reference to
-                // a String to the first 16-bit character in the String.  Skip 
-                // over the MethodTable pointer, & String 
-                // length.  Of course, the String reference points to the memory 
-                // after the sync block, so don't count that.  
+                // a String to the first 16-bit character in the String.  Skip
+                // over the MethodTable pointer, & String
+                // length.  Of course, the String reference points to the memory
+                // after the sync block, so don't count that.
                 // This property allows C#'s fixed statement to work on Strings.
                 // On 64 bit platforms, this should be 12 (8+4) and on 32 bit 8 (4+4).
 #if BIT64
@@ -197,6 +197,26 @@ namespace System.Runtime.CompilerServices
             throw new InvalidOperationException();
         }
 
+        /// <summary>
+        /// GetSubArray helper method for the compiler to slice an array using a range.
+        /// </summary>
+        public static T[] GetSubArray<T>(T[] array, Range range)
+        {
+            Type elementType = array.GetType().GetElementType();
+            Span<T> source = array.AsSpan(range);
+
+            if (elementType.IsValueType)
+            {
+                return source.ToArray();
+            }
+            else
+            {
+                T[] newArray = (T[])Array.CreateInstance(elementType, source.Length);
+                source.CopyTo(newArray);
+                return newArray;
+            }
+        }
+
         // Returns true iff the object has a component size;
         // i.e., is variable length like System.String or Array.
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -228,7 +248,7 @@ namespace System.Runtime.CompilerServices
             // This is not ideal in terms of minimizing instruction count but is the best we can do at the moment.
 
             return Unsafe.Add(ref Unsafe.As<byte, IntPtr>(ref obj.GetRawData()), -1);
-            
+
             // The JIT currently implements this as:
             // lea tmp, [rax + 8h] ; assume rax contains the object reference, tmp is type IntPtr&
             // mov tmp, qword ptr [tmp - 8h] ; tmp now contains the MethodTable* pointer
index c249ffa..c76dbe7 100644 (file)
                     "name": "System.Tests.TimeSpanTests.Parse",
                     "reason": "Temporary disabling till merging the PR https://github.com/dotnet/corefx/pull/34561"
                 },
-                
                 {
                     "name": "System.Tests.SingleTests.Test_ToString",
                     "reason" : "https://github.com/dotnet/coreclr/pull/22040"
index 022f196..ff87ba7 100644 (file)
@@ -47,6 +47,9 @@
         <ExcludeList Include="$(XunitTestBinBase)/JIT/Regression/JitBlue/GitHub_11408/GitHub_11408/*">
             <Issue>11408</Issue>
         </ExcludeList>
+        <ExcludeList Include="$(XunitTestBinBase)/JIT/Regression/JitBlue/GitHub_20958/GitHub_20958/*">
+            <Issue>22410</Issue>
+        </ExcludeList>
         <ExcludeList Include="$(XunitTestBinBase)/baseservices/exceptions/StackTracePreserve/StackTracePreserveTests/*">
             <Issue>20322</Issue>
         </ExcludeList>
         </ExcludeList>
         <ExcludeList Include="$(XunitTestBinBase)/JIT/Methodical/tailcall_v4/hijacking/*">
             <Issue>needs triage</Issue>
-        </ExcludeList>      
+        </ExcludeList>
         <ExcludeList Include="$(XunitTestBinBase)/JIT/Methodical/xxobj/sizeof/_il_dbgsizeof/*">
             <Issue>needs triage</Issue>
-        </ExcludeList>        
+        </ExcludeList>
         <ExcludeList Include="$(XunitTestBinBase)/JIT/Methodical/xxobj/sizeof/_il_dbgsizeof32/*">
             <Issue>needs triage</Issue>
         </ExcludeList>
         <ExcludeList Include="$(XunitTestBinBase)/JIT/Methodical/xxobj/sizeof/_il_dbgsizeof64/*">
             <Issue>needs triage</Issue>
-        </ExcludeList>        
+        </ExcludeList>
         <ExcludeList Include="$(XunitTestBinBase)/JIT/Methodical/xxobj/sizeof/_il_relsizeof/*">
             <Issue>needs triage</Issue>
         </ExcludeList>
         <ExcludeList Include="$(XunitTestBinBase)/JIT/Methodical/xxobj/sizeof/_il_relsizeof32/*">
             <Issue>needs triage</Issue>
-        </ExcludeList>        
+        </ExcludeList>
         <ExcludeList Include="$(XunitTestBinBase)/JIT/Methodical/xxobj/sizeof/_il_relsizeof64/*">
             <Issue>needs triage</Issue>
         </ExcludeList>
         </ExcludeList>
         <ExcludeList Include="$(XunitTestBinBase)/JIT/jit64/opt/cse/HugeArray/*">
             <Issue>needs triage</Issue>
-        </ExcludeList>      
+        </ExcludeList>
         <ExcludeList Include="$(XunitTestBinBase)/JIT/jit64/opt/cse/HugeArray1/*">
             <Issue>needs triage</Issue>
-        </ExcludeList>        
+        </ExcludeList>
         <ExcludeList Include="$(XunitTestBinBase)/JIT/jit64/opt/cse/HugeField1/*">
             <Issue>needs triage</Issue>
         </ExcludeList>
         <ExcludeList Include="$(XunitTestBinBase)/JIT/jit64/opt/cse/HugeField2/*">
             <Issue>needs triage</Issue>
-        </ExcludeList>        
+        </ExcludeList>
         <ExcludeList Include="$(XunitTestBinBase)/CoreMangLib/cti/system/string/StringFormat1/*">
             <Issue>needs triage</Issue>
         </ExcludeList>
         <ExcludeList Include="$(XunitTestBinBase)/CoreMangLib/cti/system/string/StringFormat2/*">
             <Issue>needs triage</Issue>
-        </ExcludeList>        
+        </ExcludeList>
         <ExcludeList Include="$(XunitTestBinBase)/JIT/HardwareIntrinsics/Arm64/Simd/*">
             <Issue>18895</Issue>
         </ExcludeList>
             <Issue>2420. x86 JIT doesn't support implicit tail call optimization or tail. call pop ret sequence</Issue>
         </ExcludeList>
         <ExcludeList Include="$(XunitTestBinBase)/JIT/Regression/JitBlue/DevDiv_255294/DevDiv_255294/*">
-            <Issue>11469, The test causes OutOfMemory exception in crossgen mode.</Issue> 
+            <Issue>11469, The test causes OutOfMemory exception in crossgen mode.</Issue>
         </ExcludeList>
         <ExcludeList Include="$(XunitTestBinBase)/baseservices/varargs/varargsupport/*">
             <Issue>Varargs supported on this platform</Issue>
             <Issue>22015</Issue>
         </ExcludeList>
     </ItemGroup>
-    
+
 
     <!-- Tests that need to be triaged for vararg usage as that is not supported -->
     <!-- Note these will only be excluded for unix platforms -->
     </ItemGroup>
 
     <!-- Failures while testing via ILLINK -->
-    
+
     <ItemGroup Condition="'$(XunitTestBinBase)' != '' and '$(RunTestsViaIllink)' == 'true'">
         <ExcludeList Include="$(XunitTestBinBase)/JIT/superpmi/superpmicollect/*">
             <!-- Linker runs reset CORE_ROOT to the linked directory based on superpmicollect.exe.
index 74ea90e..cf5a4e3 100644 (file)
@@ -14,7 +14,7 @@ public class GitHub_20958
     public static int IndexerWithRangeTest()
     {
         int returnVal = 100;
-   
+
         ReadOnlySpan<char> span = "Hello".AsSpan();
         ReadOnlySpan<char> sliced = span[Range.Create(new Index(1, fromEnd: false), new Index(1, fromEnd: true))];
         if (span.Slice(1, 3) != sliced)
index 887506e..9767b98 100644 (file)
 // See the LICENSE file in the project root for more information.
 
 using System.Diagnostics;
+using System.Runtime.CompilerServices;
 
 namespace System
 {
+    /// <summary>Represent a type can be used to index a collection either from the start or the end.</summary>
+    /// <remarks>
+    /// Index is used by the C# compiler to support the new index syntax
+    /// <code>
+    /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ;
+    /// int lastElement = someArray[^1]; // lastElement = 5
+    /// </code>
+    /// </remarks>
     public readonly struct Index : IEquatable<Index>
     {
         private readonly int _value;
 
-        public Index(int value, bool fromEnd)
+        /// <summary>Construct an Index using a value and indicating if the index is from the start or from the end.</summary>
+        /// <param name="value">The index value. it has to be zero or positive number.</param>
+        /// <param name="fromEnd">Indicating if the index is from the start or from the end.</param>
+        /// <remarks>
+        /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element.
+        /// </remarks>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public Index(int value, bool fromEnd = false)
         {
             if (value < 0)
             {
                 ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException();
             }
 
-            _value = fromEnd ? ~value : value;
+            if (fromEnd)
+                _value = ~value;
+            else
+                _value = value;
         }
 
-        public int Value => _value < 0 ? ~_value : _value;
-        public bool FromEnd => _value < 0;
+        // The following private constructors mainly created for perf reason to avoid the checks
+        private Index(int value)
+        {
+            _value = value;
+        }
+
+        /// <summary>Create an Index pointing at first element.</summary>
+        public static Index Start => new Index(0);
+
+        /// <summary>Create an Index pointing at beyond last element.</summary>
+        public static Index End => new Index(~0);
+
+        /// <summary>Create an Index from the start at the position indicated by the value.</summary>
+        /// <param name="value">The index value from the start.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static Index FromStart(int value)
+        {
+            if (value < 0)
+            {
+                ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException();
+            }
+
+            return new Index(value);
+        }
+
+        /// <summary>Create an Index from the end at the position indicated by the value.</summary>
+        /// <param name="value">The index value from the end.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static Index FromEnd(int value)
+        {
+            if (value < 0)
+            {
+                ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException();
+            }
+
+            return new Index(~value);
+        }
+
+        /// <summary>Returns the index value.</summary>
+        public int Value
+        {
+            get
+            {
+                if (_value < 0)
+                    return ~_value;
+                else
+                    return _value;
+            }
+        }
+
+        /// <summary>Indicates whether the index is from the start or the end.</summary>
+        public bool IsFromEnd => _value < 0;
+
+        /// <summary>Calculate the offset from the start using the giving collection length.</summary>
+        /// <param name="length">The length of the collection that the Index will be used with. length has to be a positive value</param>
+        /// <remarks>
+        /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values.
+        /// we don't validate either the returned offset is greater than the input length.
+        /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and
+        /// then used to index a collection will get out of range exception which will be same affect as the validation.
+        /// </remarks>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public int GetOffset(int length)
+        {
+            int offset;
+
+            if (IsFromEnd)
+                offset = length - (~_value);
+            else
+                offset = _value;
+
+            return offset;
+        }
+
+        /// <summary>Indicates whether the current Index object is equal to another object of the same type.</summary>
+        /// <param name="value">An object to compare with this object</param>
         public override bool Equals(object value) => value is Index && _value == ((Index)value)._value;
+
+        /// <summary>Indicates whether the current Index object is equal to another Index object.</summary>
+        /// <param name="other">An object to compare with this object</param>
         public bool Equals (Index other) => _value == other._value;
 
-        public override int GetHashCode()
+        /// <summary>Returns the hash code for this instance.</summary>
+        public override int GetHashCode() => _value;
+
+        /// <summary>Converts integer number to an Index.</summary>
+        public static implicit operator Index(int value) => FromStart(value);
+
+        /// <summary>Converts the value of the current Index object to its equivalent string representation.</summary>
+        public override string ToString()
         {
-            return _value;
-        }
+            if (IsFromEnd)
+                return ToStringFromEnd();
 
-        public override string ToString() => FromEnd ? ToStringFromEnd() : ((uint)Value).ToString();
+            return ((uint)Value).ToString();
+        }
 
         private string ToStringFromEnd()
         {
@@ -41,7 +145,5 @@ namespace System
             return new string(span.Slice(0, charsWritten + 1));
         }
 
-        public static implicit operator Index(int value)
-            => new Index(value, fromEnd: false);
     }
 }
index 033b806..ba31a6a 100644 (file)
@@ -30,7 +30,7 @@ namespace System
         private readonly object _object;
         private readonly int _index;
         private readonly int _length;
-        
+
         /// <summary>
         /// Creates a new memory over the entirety of the target array.
         /// </summary>
@@ -259,6 +259,35 @@ namespace System
         }
 
         /// <summary>
+        /// Forms a slice out of the given memory, beginning at 'startIndex'
+        /// </summary>
+        /// <param name="startIndex">The index at which to begin this slice.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public Memory<T> Slice(Index startIndex)
+        {
+            int actualIndex = startIndex.GetOffset(_length);
+            return Slice(actualIndex);
+        }
+
+        /// <summary>
+        /// Forms a slice out of the given memory using the range start and end indexes.
+        /// </summary>
+        /// <param name="range">The range used to slice the memory using its start and end indexes.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public Memory<T> Slice(Range range)
+        {
+            (int start, int length) = range.GetOffsetAndLength(_length);
+            // It is expected for _index + start to be negative if the memory is already pre-pinned.
+            return new Memory<T>(_object, _index + start, length);
+        }
+
+        /// <summary>
+        /// Forms a slice out of the given memory using the range start and end indexes.
+        /// </summary>
+        /// <param name="range">The range used to slice the memory using its start and end indexes.</param>
+        public Memory<T> this[Range range] => Slice(range);
+
+        /// <summary>
         /// Returns a span from the memory.
         /// </summary>
         public unsafe Span<T> Span
@@ -336,7 +365,7 @@ namespace System
                         ThrowHelper.ThrowArgumentOutOfRangeException();
                     }
 #endif
-                    
+
                     refToReturn = ref Unsafe.Add(ref refToReturn, desiredStartIndex);
                     lengthOfUnderlyingSpan = desiredLength;
                 }
index 11980fb..1e1fd90 100644 (file)
@@ -401,6 +401,54 @@ namespace System
         }
 
         /// <summary>
+        /// Creates a new span over the portion of the target array.
+        /// </summary>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static Span<T> AsSpan<T>(this T[] array, Index startIndex)
+        {
+            if (array == null)
+            {
+                if (!startIndex.Equals(Index.Start))
+                    ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+
+                return default;
+            }
+
+            if (default(T) == null && array.GetType() != typeof(T[]))
+                ThrowHelper.ThrowArrayTypeMismatchException();
+
+            int actualIndex = startIndex.GetOffset(array.Length);
+            if ((uint)actualIndex > (uint)array.Length)
+                ThrowHelper.ThrowArgumentOutOfRangeException();
+
+            return new Span<T>(ref Unsafe.Add(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()), actualIndex), array.Length - actualIndex);
+        }
+
+        /// <summary>
+        /// Creates a new span over the portion of the target array.
+        /// </summary>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static Span<T> AsSpan<T>(this T[] array, Range range)
+        {
+            if (array == null)
+            {
+                Index startIndex = range.Start;
+                Index endIndex = range.End;
+
+                if (!startIndex.Equals(Index.Start) || !endIndex.Equals(Index.Start))
+                    ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+
+                return default;
+            }
+
+            if (default(T) == null && array.GetType() != typeof(T[]))
+                ThrowHelper.ThrowArrayTypeMismatchException();
+
+            (int start, int length) = range.GetOffsetAndLength(array.Length);
+            return new Span<T>(ref Unsafe.Add(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()), start), length);
+        }
+
+        /// <summary>
         /// Creates a new readonly span over the portion of the target string.
         /// </summary>
         /// <param name="text">The target string.</param>
@@ -506,6 +554,26 @@ namespace System
 
         /// <summary>Creates a new <see cref="ReadOnlyMemory{T}"/> over the portion of the target string.</summary>
         /// <param name="text">The target string.</param>
+        /// <param name="startIndex">The index at which to begin this slice.</param>
+        public static ReadOnlyMemory<char> AsMemory(this string text, Index startIndex)
+        {
+            if (text == null)
+            {
+                if (!startIndex.Equals(Index.Start))
+                    ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text);
+
+                return default;
+            }
+
+            int actualIndex = startIndex.GetOffset(text.Length);
+            if ((uint)actualIndex > (uint)text.Length)
+                ThrowHelper.ThrowArgumentOutOfRangeException();
+
+            return new ReadOnlyMemory<char>(text, actualIndex, text.Length - actualIndex);
+        }
+
+        /// <summary>Creates a new <see cref="ReadOnlyMemory{T}"/> over the portion of the target string.</summary>
+        /// <param name="text">The target string.</param>
         /// <param name="start">The index at which to begin this slice.</param>
         /// <param name="length">The desired length for the slice (exclusive).</param>
         /// <remarks>Returns default when <paramref name="text"/> is null.</remarks>
@@ -532,5 +600,25 @@ namespace System
 
             return new ReadOnlyMemory<char>(text, start, length);
         }
+
+        /// <summary>Creates a new <see cref="ReadOnlyMemory{T}"/> over the portion of the target string.</summary>
+        /// <param name="text">The target string.</param>
+        /// <param name="range">The range used to indicate the start and length of the sliced string.</param>
+        public static ReadOnlyMemory<char> AsMemory(this string text, Range range)
+        {
+            if (text == null)
+            {
+                Index startIndex = range.Start;
+                Index endIndex = range.End;
+
+                if (!startIndex.Equals(Index.Start) || !endIndex.Equals(Index.Start))
+                    ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text);
+
+                return default;
+            }
+
+            (int start, int length) = range.GetOffsetAndLength(text.Length);
+            return new ReadOnlyMemory<char>(text, start, length);
+        }
     }
 }
index 34b49d4..869123a 100644 (file)
@@ -125,7 +125,7 @@ namespace System
         }
 
         /// <summary>
-        /// Removes all leading and trailing occurrences of a set of characters specified 
+        /// Removes all leading and trailing occurrences of a set of characters specified
         /// in a readonly span from the span.
         /// </summary>
         /// <param name="span">The source span from which the characters are removed.</param>
@@ -137,7 +137,7 @@ namespace System
         }
 
         /// <summary>
-        /// Removes all leading occurrences of a set of characters specified 
+        /// Removes all leading occurrences of a set of characters specified
         /// in a readonly span from the span.
         /// </summary>
         /// <param name="span">The source span from which the characters are removed.</param>
@@ -166,7 +166,7 @@ namespace System
         }
 
         /// <summary>
-        /// Removes all trailing occurrences of a set of characters specified 
+        /// Removes all trailing occurrences of a set of characters specified
         /// in a readonly span from the span.
         /// </summary>
         /// <param name="span">The source span from which the characters are removed.</param>
@@ -258,7 +258,7 @@ namespace System
         }
 
         /// <summary>
-        /// Searches for the specified value and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). 
+        /// Searches for the specified value and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T).
         /// </summary>
         /// <param name="span">The span to search.</param>
         /// <param name="value">The value to search for.</param>
@@ -282,7 +282,7 @@ namespace System
         }
 
         /// <summary>
-        /// Searches for the specified sequence and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). 
+        /// Searches for the specified sequence and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T).
         /// </summary>
         /// <param name="span">The span to search.</param>
         /// <param name="value">The sequence to search for.</param>
@@ -307,7 +307,7 @@ namespace System
         }
 
         /// <summary>
-        /// Searches for the specified value and returns the index of its last occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). 
+        /// Searches for the specified value and returns the index of its last occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T).
         /// </summary>
         /// <param name="span">The span to search.</param>
         /// <param name="value">The value to search for.</param>
@@ -331,7 +331,7 @@ namespace System
         }
 
         /// <summary>
-        /// Searches for the specified sequence and returns the index of its last occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). 
+        /// Searches for the specified sequence and returns the index of its last occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T).
         /// </summary>
         /// <param name="span">The span to search.</param>
         /// <param name="value">The sequence to search for.</param>
@@ -350,7 +350,7 @@ namespace System
         }
 
         /// <summary>
-        /// Determines whether two sequences are equal by comparing the elements using IEquatable{T}.Equals(T). 
+        /// Determines whether two sequences are equal by comparing the elements using IEquatable{T}.Equals(T).
         /// </summary>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static bool SequenceEqual<T>(this Span<T> span, ReadOnlySpan<T> other)
@@ -369,7 +369,7 @@ namespace System
         }
 
         /// <summary>
-        /// Determines the relative order of the sequences being compared by comparing the elements using IComparable{T}.CompareTo(T). 
+        /// Determines the relative order of the sequences being compared by comparing the elements using IComparable{T}.CompareTo(T).
         /// </summary>
         public static int SequenceCompareTo<T>(this Span<T> span, ReadOnlySpan<T> other)
             where T : IComparable<T>
@@ -392,7 +392,7 @@ namespace System
         }
 
         /// <summary>
-        /// Searches for the specified value and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). 
+        /// Searches for the specified value and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T).
         /// </summary>
         /// <param name="span">The span to search.</param>
         /// <param name="value">The value to search for.</param>
@@ -416,7 +416,7 @@ namespace System
         }
 
         /// <summary>
-        /// Searches for the specified sequence and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). 
+        /// Searches for the specified sequence and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T).
         /// </summary>
         /// <param name="span">The span to search.</param>
         /// <param name="value">The sequence to search for.</param>
@@ -441,7 +441,7 @@ namespace System
         }
 
         /// <summary>
-        /// Searches for the specified value and returns the index of its last occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). 
+        /// Searches for the specified value and returns the index of its last occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T).
         /// </summary>
         /// <param name="span">The span to search.</param>
         /// <param name="value">The value to search for.</param>
@@ -465,7 +465,7 @@ namespace System
         }
 
         /// <summary>
-        /// Searches for the specified sequence and returns the index of its last occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). 
+        /// Searches for the specified sequence and returns the index of its last occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T).
         /// </summary>
         /// <param name="span">The span to search.</param>
         /// <param name="value">The sequence to search for.</param>
@@ -539,7 +539,7 @@ namespace System
         }
 
         /// <summary>
-        /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1. 
+        /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1.
         /// </summary>
         /// <param name="span">The span to search.</param>
         /// <param name="values">The set of values to search for.</param>
@@ -661,7 +661,7 @@ namespace System
         }
 
         /// <summary>
-        /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1. 
+        /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1.
         /// </summary>
         /// <param name="span">The span to search.</param>
         /// <param name="value0">One of the values to search for.</param>
@@ -690,7 +690,7 @@ namespace System
         }
 
         /// <summary>
-        /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1. 
+        /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1.
         /// </summary>
         /// <param name="span">The span to search.</param>
         /// <param name="values">The set of values to search for.</param>
@@ -829,7 +829,7 @@ namespace System
         }
 
         /// <summary>
-        /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1. 
+        /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1.
         /// </summary>
         /// <param name="span">The span to search.</param>
         /// <param name="values">The set of values to search for.</param>
@@ -868,7 +868,7 @@ namespace System
         }
 
         /// <summary>
-        /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1. 
+        /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1.
         /// </summary>
         /// <param name="span">The span to search.</param>
         /// <param name="value0">One of the values to search for.</param>
@@ -890,7 +890,7 @@ namespace System
         }
 
         /// <summary>
-        /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1. 
+        /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1.
         /// </summary>
         /// <param name="span">The span to search.</param>
         /// <param name="values">The set of values to search for.</param>
@@ -909,7 +909,7 @@ namespace System
         }
 
         /// <summary>
-        /// Determines whether two sequences are equal by comparing the elements using IEquatable{T}.Equals(T). 
+        /// Determines whether two sequences are equal by comparing the elements using IEquatable{T}.Equals(T).
         /// </summary>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static bool SequenceEqual<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> other)
@@ -927,7 +927,7 @@ namespace System
         }
 
         /// <summary>
-        /// Determines the relative order of the sequences being compared by comparing the elements using IComparable{T}.CompareTo(T). 
+        /// Determines the relative order of the sequences being compared by comparing the elements using IComparable{T}.CompareTo(T).
         /// </summary>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static int SequenceCompareTo<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> other)
@@ -1134,6 +1134,19 @@ namespace System
 
         /// <summary>
         /// Creates a new Span over the portion of the target array beginning
+        /// at 'startIndex' and ending at the end of the segment.
+        /// </summary>
+        /// <param name="segment">The target array.</param>
+        /// <param name="startIndex">The index at which to begin the Span.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static Span<T> AsSpan<T>(this ArraySegment<T> segment, Index startIndex)
+        {
+            int actualIndex = startIndex.GetOffset(segment.Count);
+            return AsSpan(segment, actualIndex);
+        }
+
+        /// <summary>
+        /// Creates a new Span over the portion of the target array beginning
         /// at 'start' index and ending at 'end' index (exclusive).
         /// </summary>
         /// <param name="segment">The target array.</param>
@@ -1156,6 +1169,18 @@ namespace System
         }
 
         /// <summary>
+        /// Creates a new Span over the portion of the target array using the range start and end indexes
+        /// </summary>
+        /// <param name="segment">The target array.</param>
+        /// <param name="range">The range which has start and end indexes to use for slicing the array.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static Span<T> AsSpan<T>(this ArraySegment<T> segment, Range range)
+        {
+            (int start, int length) = range.GetOffsetAndLength(segment.Count);
+            return new Span<T>(segment.Array, segment.Offset + start, length);
+        }
+
+        /// <summary>
         /// Creates a new memory over the target array.
         /// </summary>
         public static Memory<T> AsMemory<T>(this T[] array) => new Memory<T>(array);
@@ -1174,6 +1199,24 @@ namespace System
         public static Memory<T> AsMemory<T>(this T[] array, int start) => new Memory<T>(array, start);
 
         /// <summary>
+        /// Creates a new memory over the portion of the target array starting from
+        /// 'startIndex' to the end of the array.
+        /// </summary>
+        public static Memory<T> AsMemory<T>(this T[] array, Index startIndex)
+        {
+            if (array == null)
+            {
+                if (!startIndex.Equals(Index.Start))
+                    ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+
+                return default;
+            }
+
+            int actualIndex = startIndex.GetOffset(array.Length);
+            return new Memory<T>(array, actualIndex);
+        }
+
+        /// <summary>
         /// Creates a new memory over the portion of the target array beginning
         /// at 'start' index and ending at 'end' index (exclusive).
         /// </summary>
@@ -1188,6 +1231,26 @@ namespace System
         public static Memory<T> AsMemory<T>(this T[] array, int start, int length) => new Memory<T>(array, start, length);
 
         /// <summary>
+        /// Creates a new memory over the portion of the target array beginning at inclusive start index of the range
+        /// and ending at the exclusive end index of the range.
+        /// </summary>
+        public static Memory<T> AsMemory<T>(this T[] array, Range range)
+        {
+            if (array == null)
+            {
+                Index startIndex = range.Start;
+                Index endIndex = range.End;
+                if (!startIndex.Equals(Index.Start) || !endIndex.Equals(Index.Start))
+                    ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+
+                return default;
+            }
+
+            (int start, int length) = range.GetOffsetAndLength(array.Length);
+            return new Memory<T>(array, start, length);
+        }
+
+        /// <summary>
         /// Creates a new memory over the portion of the target array.
         /// </summary>
         public static Memory<T> AsMemory<T>(this ArraySegment<T> segment) => new Memory<T>(segment.Array, segment.Offset, segment.Count);
@@ -1237,7 +1300,7 @@ namespace System
         /// Copies the contents of the array into the span. If the source
         /// and destinations overlap, this method behaves as if the original values in
         /// a temporary location before the destination is overwritten.
-        /// 
+        ///
         ///<param name="source">The array to copy items from.</param>
         /// <param name="destination">The span to copy items into.</param>
         /// <exception cref="System.ArgumentException">
@@ -1254,7 +1317,7 @@ namespace System
         /// Copies the contents of the array into the memory. If the source
         /// and destinations overlap, this method behaves as if the original values are in
         /// a temporary location before the destination is overwritten.
-        /// 
+        ///
         ///<param name="source">The array to copy items from.</param>
         /// <param name="destination">The memory to copy items into.</param>
         /// <exception cref="System.ArgumentException">
@@ -1353,16 +1416,16 @@ namespace System
         //      nuint x2 = xLength
         //      nuint y1 = (nuint)Unsafe.ByteOffset(xRef, yRef)
         //      nuint y2 = y1 + yLength
-        //  
+        //
         //  xRef relative to xRef is 0.
-        //  
+        //
         //  x2 is simply x1 + xLength. This cannot overflow.
-        //  
+        //
         //  yRef relative to xRef is (yRef - xRef). If (yRef - xRef) is
         //  negative, casting it to an unsigned 32-bit integer turns it into
         //  (yRef - xRef + 2³²). So, in the example above, y1 moves to the right
         //  of x2.
-        //  
+        //
         //  y2 is simply y1 + yLength. Note that this can overflow, as in the
         //  example above, which must be avoided.
         //
@@ -1389,11 +1452,11 @@ namespace System
         //  integers:
         //
         //      == (y1 < xLength) || (y1 > -yLength)
-        //  
+        //
         //  Due to modulo arithmetic, this gives exactly same result *except* if
         //  yLength is zero, since 2³² - 0 is 0 and not 2³². So the case
         //  y.IsEmpty must be handled separately first.
-        //  
+        //
 
         /// <summary>
         /// Determines whether two sequences overlap in memory.
index b858da2..0098dea 100644 (file)
@@ -3,20 +3,38 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Diagnostics;
+using System.Runtime.CompilerServices;
 
 namespace System
 {
+    /// <summary>Represent a range has start and end indexes.</summary>
+    /// <remarks>
+    /// Range is used by the C# compiler to support the range syntax.
+    /// <code>
+    /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 };
+    /// int[] subArray1 = someArray[0..2]; // { 1, 2 }
+    /// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 }
+    /// </code>
+    /// </remarks>
     public readonly struct Range : IEquatable<Range>
     {
+        /// <summary>Represent the inclusive start index of the Range.</summary>
         public Index Start { get; }
+
+        /// <summary>Represent the exclusive end index of the Range.</summary>
         public Index End { get; }
 
-        private Range(Index start, Index end)
+        /// <summary>Construct a Range object using the start and end indexes.</summary>
+        /// <param name="start">Represent the inclusive start index of the range.</param>
+        /// <param name="end">Represent the exclusive end index of the range.</param>
+        public Range(Index start, Index end)
         {
             Start = start;
             End = end;
         }
 
+        /// <summary>Indicates whether the current Range object is equal to another object of the same type.</summary>
+        /// <param name="value">An object to compare with this object</param>
         public override bool Equals(object value)
         {
             if (value is Range)
@@ -28,20 +46,24 @@ namespace System
             return false;
         }
 
+        /// <summary>Indicates whether the current Range object is equal to another Range object.</summary>
+        /// <param name="other">An object to compare with this object</param>
         public bool Equals (Range other) => other.Start.Equals(Start) && other.End.Equals(End);
 
+        /// <summary>Returns the hash code for this instance.</summary>
         public override int GetHashCode()
         {
             return HashCode.Combine(Start.GetHashCode(), End.GetHashCode());
         }
 
+        /// <summary>Converts the value of the current Range object to its equivalent string representation.</summary>
         public override string ToString()
         {
             Span<char> span = stackalloc char[2 + (2 * 11)]; // 2 for "..", then for each index 1 for '^' and 10 for longest possible uint
             int charsWritten;
             int pos = 0;
 
-            if (Start.FromEnd)
+            if (Start.IsFromEnd)
             {
                 span[0] = '^';
                 pos = 1;
@@ -53,7 +75,7 @@ namespace System
             span[pos++] = '.';
             span[pos++] = '.';
 
-            if (End.FromEnd)
+            if (End.IsFromEnd)
             {
                 span[pos++] = '^';
             }
@@ -64,9 +86,63 @@ namespace System
             return new string(span.Slice(0, pos));
         }
 
-        public static Range Create(Index start, Index end) => new Range(start, end);
-        public static Range FromStart(Index start) => new Range(start, new Index(0, fromEnd: true));
-        public static Range ToEnd(Index end) => new Range(new Index(0, fromEnd: false), end);
-        public static Range All() => new Range(new Index(0, fromEnd: false), new Index(0, fromEnd: true));
+        /// <summary>Create a Range object starting from start index to the end of the collection.</summary>
+        public static Range StartAt(Index start) => new Range(start, Index.End);
+
+        /// <summary>Create a Range object starting from first element in the collection to the end Index.</summary>
+        public static Range EndAt(Index end) => new Range(Index.Start, end);
+
+        /// <summary>Create a Range object starting from first element to the end.</summary>
+        public static Range All => new Range(Index.Start, Index.End);
+
+        /// <summary>Destruct the range object according to a collection length and return the start offset from the beginning and the length of this range.</summary>
+        /// <param name="length">The length of the collection that the range will be used with. length has to be a positive value</param>
+        /// <remarks>
+        /// For performance reason, we don't validate the input length parameter against negative values.
+        /// It is expected Range will be used with collections which always have non negative length/count.
+        /// We validate the range is inside the length scope though.
+        /// </remarks>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public OffsetAndLength GetOffsetAndLength(int length)
+        {
+            int start;
+            Index startIndex = Start;
+            if (startIndex.IsFromEnd)
+                start = length - startIndex.Value;
+            else
+                start = startIndex.Value;
+
+            int end;
+            Index endIndex = End;
+            if (endIndex.IsFromEnd)
+                end = length - endIndex.Value;
+            else
+                end = endIndex.Value;
+
+            if ((uint)end > (uint)length || (uint)start > (uint)end)
+            {
+                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length);
+            }
+
+            return new OffsetAndLength(start, end - start);
+        }
+
+        public readonly struct OffsetAndLength
+        {
+            public int Offset { get; }
+            public int Length { get; }
+
+            public OffsetAndLength(int offset, int length)
+            {
+                Offset = offset;
+                Length = length;
+            }
+
+            public void Deconstruct(out int offset, out int length)
+            {
+                offset = Offset;
+                length = Length;
+            }
+        }
     }
 }
index 8fd659a..6c59843 100644 (file)
@@ -188,6 +188,35 @@ namespace System
         }
 
         /// <summary>
+        /// Forms a slice out of the given memory, beginning at 'startIndex'
+        /// </summary>
+        /// <param name="startIndex">The index at which to begin this slice.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public ReadOnlyMemory<T> Slice(Index startIndex)
+        {
+            int actualIndex = startIndex.GetOffset(_length);
+            return Slice(actualIndex);
+        }
+
+        /// <summary>
+        /// Forms a slice out of the given memory using the range start and end indexes.
+        /// </summary>
+        /// <param name="range">The range used to slice the memory using its start and end indexes.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public ReadOnlyMemory<T> Slice(Range range)
+        {
+            (int start, int length) = range.GetOffsetAndLength(_length);
+            // It is expected for _index + start to be negative if the memory is already pre-pinned.
+            return new ReadOnlyMemory<T>(_object, _index + start, length);
+        }
+
+        /// <summary>
+        /// Forms a slice out of the given memory using the range start and end indexes.
+        /// </summary>
+        /// <param name="range">The range used to slice the memory using its start and end indexes.</param>
+        public ReadOnlyMemory<T> this[Range range] => Slice(range);
+
+        /// <summary>
         /// Returns a span from the memory.
         /// </summary>
         public unsafe ReadOnlySpan<T> Span
@@ -386,7 +415,7 @@ namespace System
             // 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;
         }
-        
+
         /// <summary>Gets the state of the memory as individual fields.</summary>
         /// <param name="start">The offset.</param>
         /// <param name="length">The count.</param>
index b2ce53b..eb3fd14 100644 (file)
@@ -157,20 +157,12 @@ namespace System
             get
             {
                 // Evaluate the actual index first because it helps performance
-                int actualIndex = index.FromEnd ? _length - index.Value : index.Value;
+                int actualIndex = index.GetOffset(_length);
                 return ref this [actualIndex];
             }
         }
 
-        public ReadOnlySpan<T> this[Range range]
-        {
-            get
-            {
-                int start = range.Start.FromEnd ? _length - range.Start.Value : range.Start.Value;
-                int end = range.End.FromEnd ? _length - range.End.Value : range.End.Value;
-                return Slice(start, end - start);
-            }
-        }
+        public ReadOnlySpan<T> this[Range range] => Slice(range);
 
         /// <summary>
         /// Returns a reference to the 0th element of the Span. If the Span is empty, returns null reference.
@@ -297,6 +289,33 @@ namespace System
         }
 
         /// <summary>
+        /// Forms a slice out of the given read-only span, beginning at 'startIndex'
+        /// </summary>
+        /// <param name="startIndex">The index at which to begin this slice.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public ReadOnlySpan<T> Slice(Index startIndex)
+        {
+            int actualIndex;
+            if (startIndex.IsFromEnd)
+                actualIndex = _length - startIndex.Value;
+            else
+                actualIndex = startIndex.Value;
+
+            return Slice(actualIndex);
+        }
+
+        /// <summary>
+        /// Forms a slice out of the given read-only span, beginning at range start index to the range end
+        /// </summary>
+        /// <param name="range">The range which has the start and end indexes used to slice the span.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public ReadOnlySpan<T> Slice(Range range)
+        {
+            (int start, int length) = range.GetOffsetAndLength(_length);
+            return new ReadOnlySpan<T>(ref Unsafe.Add(ref _pointer.Value, start), length);
+        }
+
+        /// <summary>
         /// 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.
index 490767d..66de4fe 100644 (file)
@@ -163,20 +163,12 @@ namespace System
             get
             {
                 // Evaluate the actual index first because it helps performance
-                int actualIndex = index.FromEnd ? _length - index.Value : index.Value;
+                int actualIndex = index.GetOffset(_length);
                 return ref this [actualIndex];
             }
         }
 
-        public Span<T> this[Range range]
-        {
-            get
-            {
-                int start = range.Start.FromEnd ? _length - range.Start.Value : range.Start.Value;
-                int end = range.End.FromEnd ? _length - range.End.Value : range.End.Value;
-                return Slice(start, end - start);
-            }
-        }
+        public Span<T> this[Range range] => Slice(range);
 
         /// <summary>
         /// Returns a reference to the 0th element of the Span. If the Span is empty, returns null reference.
@@ -381,6 +373,28 @@ namespace System
         }
 
         /// <summary>
+        /// Forms a slice out of the given span, beginning at 'startIndex'
+        /// </summary>
+        /// <param name="startIndex">The index at which to begin this slice.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public Span<T> Slice(Index startIndex)
+        {
+            int actualIndex = startIndex.GetOffset(_length);
+            return Slice(actualIndex);
+        }
+
+        /// <summary>
+        /// Forms a slice out of the given span, beginning at range start index to the range end
+        /// </summary>
+        /// <param name="range">The range which has the start and end indexes used to slice the span.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public Span<T> Slice(Range range)
+        {
+            (int start, int length) = range.GetOffsetAndLength(_length);
+            return new Span<T>(ref Unsafe.Add(ref _pointer.Value, start), length);
+        }
+
+        /// <summary>
         /// 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.
index f183ed6..82d7422 100644 (file)
@@ -1686,6 +1686,20 @@ namespace System
             return InternalSubString(startIndex, length);
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public string Substring(Index startIndex)
+        {
+            int actualIndex = startIndex.GetOffset(Length);
+            return Substring(actualIndex);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public string Substring(Range range)
+        {
+            (int start, int length) = range.GetOffsetAndLength(Length);
+            return Substring(start, length);
+        }
+
         private unsafe string InternalSubString(int startIndex, int length)
         {
             Debug.Assert(startIndex >= 0 && startIndex <= this.Length, "StartIndex is out of range!");
index 0958e86..22f830a 100644 (file)
@@ -444,6 +444,19 @@ namespace System
             return (value == null || 0u >= (uint)value.Length) ? true : false;
         }
 
+        [System.Runtime.CompilerServices.IndexerName("Chars")]
+        public char this[Index index]
+        {
+            get
+            {
+                int actualIndex = index.GetOffset(Length);
+                return this[actualIndex];
+            }
+        }
+
+        [System.Runtime.CompilerServices.IndexerName("Chars")]
+        public string this[Range range] => Substring(range);
+
         public static bool IsNullOrWhiteSpace(string value)
         {
             if (value == null) return true;
@@ -672,7 +685,7 @@ namespace System
 
         //
         // IConvertible implementation
-        // 
+        //
 
         public TypeCode GetTypeCode()
         {
index b9276ca..1bbc7e3 100644 (file)
@@ -119,7 +119,7 @@ namespace System
 
         internal static void ThrowArgumentOutOfRangeException_ArgumentOutOfRange_Enum()
         {
-            throw GetArgumentOutOfRangeException(ExceptionArgument.type, 
+            throw GetArgumentOutOfRangeException(ExceptionArgument.type,
                                                     ExceptionResource.ArgumentOutOfRange_Enum);
         }