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],再來看一下下面的示例吧
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();輸出結果如下:
outputIndex
那么它是如何實現的呢,索引值引入了一個新的數據結構 System.Index,當你使用 ^ 運算符的時候,實際轉換成了 Index。
Index:
public?readonly?struct?Index?:?IEquatable<Index> {public?Index(int?value,?bool?fromEnd?=?false);///?<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://?????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 類型的索引器就可以了
寫一個簡單的小示例:
private?class?TestCollection {public?IList<int>?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 來指定開始和結束
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;?}///?<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>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>Calculate?the?start?offset?and?length?of?range?object?using?a?collection?length.</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>public?(int?Offset,?int?Length)?GetOffsetAndLength(int?length); }如何在自己的類中支持 Range 呢?
一種方式是自己直接實現一個類型是 Range 的索引器
另外一種方式是隱式實現,在自定義類中添加一個 Count 屬性,然后實現一個 Slice 方法,Slice 方法有兩個 int 類型的參數,第一個參數表示 offset,第二個參數表示 length
來看下面這個示例吧,還是剛才那個類,我們支持一下 Range:
private?class?TestCollection {public?IList<int>?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
https://docs.microsoft.com/en-us/dotnet/csharp/tutorials/ranges-indexes
https://docs.microsoft.com/en-us/dotnet/api/system.index?view=net-5.0
https://docs.microsoft.com/en-us/dotnet/api/system.range?view=net-5.0
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/ranges
https://github.com/dotnet/runtime/blob/master/src/libraries/System.Private.CoreLib/src/System/Index.cs
https://github.com/dotnet/runtime/blob/master/src/libraries/System.Private.CoreLib/src/System/Range.cs
https://github.com/dotnet/runtime/blob/master/src/libraries/System.Private.CoreLib/src/System/ArraySegment.cs
總結
以上是生活随笔為你收集整理的C# 使用 Index 和 Range 简化集合操作的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何在 ASP.Net Core 中使用
- 下一篇: CSRobot gen:mssql-c#