Update Range.GetOffsetAndLength (#23855)
[platform/upstream/coreclr.git] / src / System.Private.CoreLib / shared / System / Range.cs
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4
5 using System.Diagnostics;
6 using System.Runtime.CompilerServices;
7
8 namespace System
9 {
10     /// <summary>Represent a range has start and end indexes.</summary>
11     /// <remarks>
12     /// Range is used by the C# compiler to support the range syntax.
13     /// <code>
14     /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 };
15     /// int[] subArray1 = someArray[0..2]; // { 1, 2 }
16     /// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 }
17     /// </code>
18     /// </remarks>
19     public readonly struct Range : IEquatable<Range>
20     {
21         /// <summary>Represent the inclusive start index of the Range.</summary>
22         public Index Start { get; }
23
24         /// <summary>Represent the exclusive end index of the Range.</summary>
25         public Index End { get; }
26
27         /// <summary>Construct a Range object using the start and end indexes.</summary>
28         /// <param name="start">Represent the inclusive start index of the range.</param>
29         /// <param name="end">Represent the exclusive end index of the range.</param>
30         public Range(Index start, Index end)
31         {
32             Start = start;
33             End = end;
34         }
35
36         /// <summary>Indicates whether the current Range object is equal to another object of the same type.</summary>
37         /// <param name="value">An object to compare with this object</param>
38         public override bool Equals(object value)
39         {
40             if (value is Range)
41             {
42                 Range r = (Range)value;
43                 return r.Start.Equals(Start) && r.End.Equals(End);
44             }
45
46             return false;
47         }
48
49         /// <summary>Indicates whether the current Range object is equal to another Range object.</summary>
50         /// <param name="other">An object to compare with this object</param>
51         public bool Equals (Range other) => other.Start.Equals(Start) && other.End.Equals(End);
52
53         /// <summary>Returns the hash code for this instance.</summary>
54         public override int GetHashCode()
55         {
56             return HashCode.Combine(Start.GetHashCode(), End.GetHashCode());
57         }
58
59         /// <summary>Converts the value of the current Range object to its equivalent string representation.</summary>
60         public override string ToString()
61         {
62             Span<char> span = stackalloc char[2 + (2 * 11)]; // 2 for "..", then for each index 1 for '^' and 10 for longest possible uint
63             int charsWritten;
64             int pos = 0;
65
66             if (Start.IsFromEnd)
67             {
68                 span[0] = '^';
69                 pos = 1;
70             }
71             bool formatted = ((uint)Start.Value).TryFormat(span.Slice(pos), out charsWritten);
72             Debug.Assert(formatted);
73             pos += charsWritten;
74
75             span[pos++] = '.';
76             span[pos++] = '.';
77
78             if (End.IsFromEnd)
79             {
80                 span[pos++] = '^';
81             }
82             formatted = ((uint)End.Value).TryFormat(span.Slice(pos), out charsWritten);
83             Debug.Assert(formatted);
84             pos += charsWritten;
85
86             return new string(span.Slice(0, pos));
87         }
88
89         /// <summary>Create a Range object starting from start index to the end of the collection.</summary>
90         public static Range StartAt(Index start) => new Range(start, Index.End);
91
92         /// <summary>Create a Range object starting from first element in the collection to the end Index.</summary>
93         public static Range EndAt(Index end) => new Range(Index.Start, end);
94
95         /// <summary>Create a Range object starting from first element to the end.</summary>
96         public static Range All => new Range(Index.Start, Index.End);
97
98         /// <summary>Calculate the start offset and length of range object using a collection length.</summary>
99         /// <param name="length">The length of the collection that the range will be used with. length has to be a positive value.</param>
100         /// <remarks>
101         /// For performance reason, we don't validate the input length parameter against negative values.
102         /// It is expected Range will be used with collections which always have non negative length/count.
103         /// We validate the range is inside the length scope though.
104         /// </remarks>
105         [MethodImpl(MethodImplOptions.AggressiveInlining)]
106         public (int Offset, int Length) GetOffsetAndLength(int length)
107         {
108             int start;
109             Index startIndex = Start;
110             if (startIndex.IsFromEnd)
111                 start = length - startIndex.Value;
112             else
113                 start = startIndex.Value;
114
115             int end;
116             Index endIndex = End;
117             if (endIndex.IsFromEnd)
118                 end = length - endIndex.Value;
119             else
120                 end = endIndex.Value;
121
122             if ((uint)end > (uint)length || (uint)start > (uint)end)
123             {
124                 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length);
125             }
126
127             return (start, end - start);
128         }
129     }
130 }