Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit b0b1617

Browse files
tarekghstephentoub
authored andcommitted
Index and Range updates (dotnet/coreclr#22331)
* 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 Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
1 parent 8c52600 commit b0b1617

File tree

11 files changed

+518
-71
lines changed

11 files changed

+518
-71
lines changed

src/Common/src/CoreLib/System/Index.cs

Lines changed: 112 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,138 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System.Diagnostics;
6+
using System.Runtime.CompilerServices;
67

78
namespace System
89
{
10+
/// <summary>Represent a type can be used to index a collection either from the start or the end.</summary>
11+
/// <remarks>
12+
/// Index is used by the C# compiler to support the new index syntax
13+
/// <code>
14+
/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ;
15+
/// int lastElement = someArray[^1]; // lastElement = 5
16+
/// </code>
17+
/// </remarks>
918
public readonly struct Index : IEquatable<Index>
1019
{
1120
private readonly int _value;
1221

13-
public Index(int value, bool fromEnd)
22+
/// <summary>Construct an Index using a value and indicating if the index is from the start or from the end.</summary>
23+
/// <param name="value">The index value. it has to be zero or positive number.</param>
24+
/// <param name="fromEnd">Indicating if the index is from the start or from the end.</param>
25+
/// <remarks>
26+
/// 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.
27+
/// </remarks>
28+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
29+
public Index(int value, bool fromEnd = false)
1430
{
1531
if (value < 0)
1632
{
1733
ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException();
1834
}
1935

20-
_value = fromEnd ? ~value : value;
36+
if (fromEnd)
37+
_value = ~value;
38+
else
39+
_value = value;
2140
}
2241

23-
public int Value => _value < 0 ? ~_value : _value;
24-
public bool FromEnd => _value < 0;
42+
// The following private constructors mainly created for perf reason to avoid the checks
43+
private Index(int value)
44+
{
45+
_value = value;
46+
}
47+
48+
/// <summary>Create an Index pointing at first element.</summary>
49+
public static Index Start => new Index(0);
50+
51+
/// <summary>Create an Index pointing at beyond last element.</summary>
52+
public static Index End => new Index(~0);
53+
54+
/// <summary>Create an Index from the start at the position indicated by the value.</summary>
55+
/// <param name="value">The index value from the start.</param>
56+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
57+
public static Index FromStart(int value)
58+
{
59+
if (value < 0)
60+
{
61+
ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException();
62+
}
63+
64+
return new Index(value);
65+
}
66+
67+
/// <summary>Create an Index from the end at the position indicated by the value.</summary>
68+
/// <param name="value">The index value from the end.</param>
69+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
70+
public static Index FromEnd(int value)
71+
{
72+
if (value < 0)
73+
{
74+
ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException();
75+
}
76+
77+
return new Index(~value);
78+
}
79+
80+
/// <summary>Returns the index value.</summary>
81+
public int Value
82+
{
83+
get
84+
{
85+
if (_value < 0)
86+
return ~_value;
87+
else
88+
return _value;
89+
}
90+
}
91+
92+
/// <summary>Indicates whether the index is from the start or the end.</summary>
93+
public bool IsFromEnd => _value < 0;
94+
95+
/// <summary>Calculate the offset from the start using the giving collection length.</summary>
96+
/// <param name="length">The length of the collection that the Index will be used with. length has to be a positive value</param>
97+
/// <remarks>
98+
/// For performance reason, we don't validate the input length parameter and the returned offset value against negative values.
99+
/// we don't validate either the returned offset is greater than the input length.
100+
/// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and
101+
/// then used to index a collection will get out of range exception which will be same affect as the validation.
102+
/// </remarks>
103+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
104+
public int GetOffset(int length)
105+
{
106+
int offset;
107+
108+
if (IsFromEnd)
109+
offset = length - (~_value);
110+
else
111+
offset = _value;
112+
113+
return offset;
114+
}
115+
116+
/// <summary>Indicates whether the current Index object is equal to another object of the same type.</summary>
117+
/// <param name="value">An object to compare with this object</param>
25118
public override bool Equals(object value) => value is Index && _value == ((Index)value)._value;
119+
120+
/// <summary>Indicates whether the current Index object is equal to another Index object.</summary>
121+
/// <param name="other">An object to compare with this object</param>
26122
public bool Equals (Index other) => _value == other._value;
27123

28-
public override int GetHashCode()
124+
/// <summary>Returns the hash code for this instance.</summary>
125+
public override int GetHashCode() => _value;
126+
127+
/// <summary>Converts integer number to an Index.</summary>
128+
public static implicit operator Index(int value) => FromStart(value);
129+
130+
/// <summary>Converts the value of the current Index object to its equivalent string representation.</summary>
131+
public override string ToString()
29132
{
30-
return _value;
31-
}
133+
if (IsFromEnd)
134+
return ToStringFromEnd();
32135

33-
public override string ToString() => FromEnd ? ToStringFromEnd() : ((uint)Value).ToString();
136+
return ((uint)Value).ToString();
137+
}
34138

35139
private string ToStringFromEnd()
36140
{
@@ -41,7 +145,5 @@ private string ToStringFromEnd()
41145
return new string(span.Slice(0, charsWritten + 1));
42146
}
43147

44-
public static implicit operator Index(int value)
45-
=> new Index(value, fromEnd: false);
46148
}
47149
}

src/Common/src/CoreLib/System/Memory.cs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public readonly struct Memory<T>
3030
private readonly object _object;
3131
private readonly int _index;
3232
private readonly int _length;
33-
33+
3434
/// <summary>
3535
/// Creates a new memory over the entirety of the target array.
3636
/// </summary>
@@ -258,6 +258,35 @@ public Memory<T> Slice(int start, int length)
258258
return new Memory<T>(_object, _index + start, length);
259259
}
260260

261+
/// <summary>
262+
/// Forms a slice out of the given memory, beginning at 'startIndex'
263+
/// </summary>
264+
/// <param name="startIndex">The index at which to begin this slice.</param>
265+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
266+
public Memory<T> Slice(Index startIndex)
267+
{
268+
int actualIndex = startIndex.GetOffset(_length);
269+
return Slice(actualIndex);
270+
}
271+
272+
/// <summary>
273+
/// Forms a slice out of the given memory using the range start and end indexes.
274+
/// </summary>
275+
/// <param name="range">The range used to slice the memory using its start and end indexes.</param>
276+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
277+
public Memory<T> Slice(Range range)
278+
{
279+
(int start, int length) = range.GetOffsetAndLength(_length);
280+
// It is expected for _index + start to be negative if the memory is already pre-pinned.
281+
return new Memory<T>(_object, _index + start, length);
282+
}
283+
284+
/// <summary>
285+
/// Forms a slice out of the given memory using the range start and end indexes.
286+
/// </summary>
287+
/// <param name="range">The range used to slice the memory using its start and end indexes.</param>
288+
public Memory<T> this[Range range] => Slice(range);
289+
261290
/// <summary>
262291
/// Returns a span from the memory.
263292
/// </summary>
@@ -336,7 +365,7 @@ public unsafe Span<T> Span
336365
ThrowHelper.ThrowArgumentOutOfRangeException();
337366
}
338367
#endif
339-
368+
340369
refToReturn = ref Unsafe.Add(ref refToReturn, desiredStartIndex);
341370
lengthOfUnderlyingSpan = desiredLength;
342371
}

src/Common/src/CoreLib/System/MemoryExtensions.Fast.cs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,54 @@ public static Span<T> AsSpan<T>(this T[] array, int start)
400400
return new Span<T>(ref Unsafe.Add(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()), start), array.Length - start);
401401
}
402402

403+
/// <summary>
404+
/// Creates a new span over the portion of the target array.
405+
/// </summary>
406+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
407+
public static Span<T> AsSpan<T>(this T[] array, Index startIndex)
408+
{
409+
if (array == null)
410+
{
411+
if (!startIndex.Equals(Index.Start))
412+
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
413+
414+
return default;
415+
}
416+
417+
if (default(T) == null && array.GetType() != typeof(T[]))
418+
ThrowHelper.ThrowArrayTypeMismatchException();
419+
420+
int actualIndex = startIndex.GetOffset(array.Length);
421+
if ((uint)actualIndex > (uint)array.Length)
422+
ThrowHelper.ThrowArgumentOutOfRangeException();
423+
424+
return new Span<T>(ref Unsafe.Add(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()), actualIndex), array.Length - actualIndex);
425+
}
426+
427+
/// <summary>
428+
/// Creates a new span over the portion of the target array.
429+
/// </summary>
430+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
431+
public static Span<T> AsSpan<T>(this T[] array, Range range)
432+
{
433+
if (array == null)
434+
{
435+
Index startIndex = range.Start;
436+
Index endIndex = range.End;
437+
438+
if (!startIndex.Equals(Index.Start) || !endIndex.Equals(Index.Start))
439+
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
440+
441+
return default;
442+
}
443+
444+
if (default(T) == null && array.GetType() != typeof(T[]))
445+
ThrowHelper.ThrowArrayTypeMismatchException();
446+
447+
(int start, int length) = range.GetOffsetAndLength(array.Length);
448+
return new Span<T>(ref Unsafe.Add(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()), start), length);
449+
}
450+
403451
/// <summary>
404452
/// Creates a new readonly span over the portion of the target string.
405453
/// </summary>
@@ -504,6 +552,26 @@ public static ReadOnlyMemory<char> AsMemory(this string text, int start)
504552
return new ReadOnlyMemory<char>(text, start, text.Length - start);
505553
}
506554

555+
/// <summary>Creates a new <see cref="ReadOnlyMemory{T}"/> over the portion of the target string.</summary>
556+
/// <param name="text">The target string.</param>
557+
/// <param name="startIndex">The index at which to begin this slice.</param>
558+
public static ReadOnlyMemory<char> AsMemory(this string text, Index startIndex)
559+
{
560+
if (text == null)
561+
{
562+
if (!startIndex.Equals(Index.Start))
563+
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text);
564+
565+
return default;
566+
}
567+
568+
int actualIndex = startIndex.GetOffset(text.Length);
569+
if ((uint)actualIndex > (uint)text.Length)
570+
ThrowHelper.ThrowArgumentOutOfRangeException();
571+
572+
return new ReadOnlyMemory<char>(text, actualIndex, text.Length - actualIndex);
573+
}
574+
507575
/// <summary>Creates a new <see cref="ReadOnlyMemory{T}"/> over the portion of the target string.</summary>
508576
/// <param name="text">The target string.</param>
509577
/// <param name="start">The index at which to begin this slice.</param>
@@ -532,5 +600,25 @@ public static ReadOnlyMemory<char> AsMemory(this string text, int start, int len
532600

533601
return new ReadOnlyMemory<char>(text, start, length);
534602
}
603+
604+
/// <summary>Creates a new <see cref="ReadOnlyMemory{T}"/> over the portion of the target string.</summary>
605+
/// <param name="text">The target string.</param>
606+
/// <param name="range">The range used to indicate the start and length of the sliced string.</param>
607+
public static ReadOnlyMemory<char> AsMemory(this string text, Range range)
608+
{
609+
if (text == null)
610+
{
611+
Index startIndex = range.Start;
612+
Index endIndex = range.End;
613+
614+
if (!startIndex.Equals(Index.Start) || !endIndex.Equals(Index.Start))
615+
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text);
616+
617+
return default;
618+
}
619+
620+
(int start, int length) = range.GetOffsetAndLength(text.Length);
621+
return new ReadOnlyMemory<char>(text, start, length);
622+
}
535623
}
536624
}

0 commit comments

Comments
 (0)