1. 程式人生 > >C# 使用 Index 和 Range 簡化集合操作

C# 使用 Index 和 Range 簡化集合操作

# C# 使用 Index 和 Range 簡化集合操作 ## Intro 有的語言陣列的索引值是支援負數的,表示從後向前索引,比如:`arr[-1]` 從 C# 8 開始,C# 支援了陣列的反向 `Index`,和 `Range` 操作,反向 `Index` 類似於其他語言中的負索引值,但其實是由編譯器幫我們做了一個轉換,`Range` 使得我們對陣列擷取某一部分的操作會非常簡單,下面來看一下如何使用吧 ## Sample 使用 `^` 可以從集合的最後開始索引元素,如果從陣列的最後開始索引元素,最後一個元素應該是 1 而不是0如: `arr[^1]` 使用 `..` 可以基於某個陣列擷取集合中的某一段建立一個新的陣列,比如 `var newArray = array[1..^1]`,再來看一下下面的示例吧 ``` c# int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; int lastElement = someArray[^1]; // lastElement = 5 lastElement.Dump(); someArray[3..5].Dump(); someArray[1..^1].Dump(); someArray[1..].Dump(); someArray[..^1].Dump(); someArray[..2].Dump(); ``` 輸出結果如下: ![output](https://img2020.cnblogs.com/blog/489462/202102/489462-20210225014155869-984669497.png) ## Index 那麼它是如何實現的呢,索引值引入了一個新的資料結構 `System.Index`,當你使用 `^` 運算子的時候,實際轉換成了 `Index`。 `Index`: ``` c# public readonly struct Index : IEquatable { public Index(int value, bool fromEnd = false); /// Create an Index pointing at first element. public static Index Start => new Index(0); /// Create an Index pointing at beyond last element. public static Index End => new Index(~0); // // Summary: // Gets a value that indicates whether the index is from the start or the end. // // Returns: // true if the Index is from the end; otherwise, false. public bool IsFromEnd { get; } // // Summary: // Gets the index value. // // Returns: // The index value. public int Value { get; } // // Summary: // Creates an System.Index from the end of a collection at a specified index position. // // Parameters: // value: // The index value from the end of a collection. // // Returns: // The Index value. public static Index FromEnd(int value); // // Summary: // Create an System.Index from the specified index at the start of a collection. // // Parameters: // value: // The index position from the start of a collection. // // Returns: // The Index value. public static Index FromStart(int value); // // Summary: // Returns a value that indicates whether the current object is equal to another // System.Index object. // // Parameters: // other: // The object to compare with this instance. // // Returns: // true if the current Index object is equal to other; false otherwise. public bool Equals(Index other); // // Summary: // Calculates the offset from the start of the collection using the given collection length. // // Parameters: // length: // The length of the collection that the Index will be used with. Must be a positive value. // // Returns: // The offset. public int GetOffset(int length); // // Summary: // Converts integer number to an Index. // // Parameters: // value: // The integer to convert. // // Returns: // An Index representing the integer. public static implicit operator Index(int value); } ``` 如果想要自己自定義的集合支援 `Index` 這種從陣列最後索引的特性,只需要加一個型別是 `Index` 的索引器就可以了,正向索引也是支援的,int 會自動隱式轉換為 `Index`,除了顯示的增加 `Index` 索引器之外,還可以隱式支援,實現一個 `int Count {get;}` 的屬性(屬性名叫 `Length` 也可以),再實現一個 `int` 型別的索引器就可以了 寫一個簡單的小示例: ``` c# private class TestCollection { public IList Data { get; init; } public int Count => Data.Count; public int this[int index] => Data[index]; //public int this[Index index] => Data[index.GetOffset(Data.Count)]; } var array = new TestCollection() { Data = new[] { 1, 2, 3 } }; Console.WriteLine(array[^1]); Console.WriteLine(array[1]); ``` ## Range `Range` 是在 `Index` 的基礎上實現的,`Range` 需要兩個 `Index` 來指定開始和結束 ``` c# public readonly struct Range : IEquatable { /// Represent the inclusive start index of the Range.
public Index Start { get; } /// Represent the exclusive end index of the Range. public Index End { get; } /// Construct a Range object using the start and end indexes. /// Represent the inclusive start index of the range. /// Represent the exclusive end index of the range. public Range(Index start, Index end) { Start = start; End = end; } /// Create a Range object starting from start index to the end of the collection.
public static Range StartAt(Index start) => new Range(start, Index.End); /// Create a Range object starting from first element in the collection to the end Index. public static Range EndAt(Index end) => new Range(Index.Start, end); /// Create a Range object starting from first element to the end.
public static Range All => new Range(Index.Start, Index.End); /// Calculate the start offset and length of range object using a collection length. /// The length of the collection that the range will be used with. length has to be a positive value. /// /// 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. /// public (int Offset, int Length) GetOffsetAndLength(int length); } ``` 如何在自己的類中支援 `Range` 呢? 一種方式是自己直接實現一個型別是 `Range` 的索引器 另外一種方式是隱式實現,在自定義類中新增一個 `Count` 屬性,然後實現一個 `Slice` 方法,`Slice` 方法有兩個 `int` 型別的引數,第一個引數表示 offset,第二個引數表示 length 或者 count 來看下面這個示例吧,還是剛才那個類,我們支援一下 `Range`: ``` c# private class TestCollection { public IList Data { get; init; } //public int[] this[Range range] //{ // get // { // var rangeInfo = range.GetOffsetAndLength(Data.Count); // return Data.Skip(rangeInfo.Offset).Take(rangeInfo.Length).ToArray(); // } //} public int Count => Data.Count; public int[] Slice(int start, int length) { var array = new int[length]; for (var i = start; i < length && i < Data.Count; i++) { array[i] = Data[i]; } return array; } } ``` ## More 新的操作符 (`^` and `..`) 都只是語法糖,本質上是呼叫 `Index`、`Range` `Index` 並不是支援負數索引,從最後向前索引只是編譯器幫我們做了一個轉換,轉換成從前到後的索引值,藉助於它,我們很多取集合最後一個元素的寫法就可以大大的簡化了,就可以從原來的 `array[array.Length-1]` => `array[^1]` `Range` 在建立某個陣列的子序列的時候就非常的方便, `newArray = array[1..3]`,需要注意的是,`Range` 是"左閉右開"的,包含左邊界的值,不包含右邊界的值 還沒使用過 `Index`/`Range` 的快去體驗一下吧,用它們優化陣列的操作吧~~ ## References - - - - - - -