在 C# 8 之前,要實(shí)現(xiàn)諸如“訪問序列的倒數(shù)第二個元素””獲取由第三個至第五個元素之間的元素組成的新序列”之類的功能是比較麻煩的,不夠簡潔直觀。對此,C# 8 引入了索引和范圍概念,極大簡化了此類操作。
索引
C# 8 引入了新類型 System.Index 用于表示序列的索引,可以直接由 int 類型自動轉(zhuǎn)換賦值。
int[] fibSequence = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 }; // 代表第3個元素的索引,起始索引為 0 Index index1 = 2; // 輸出: 2 Console.WriteLine(fib)
看到這兒,性急的小伙伴應(yīng)該要罵娘了,這不是多此一舉嘛!直接用數(shù)字索引訪問不是更好?
別著急,慢慢來,我們還可以在索引前面加 ^ 從后往前索引。
int[] fibSequence = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 }; // 第3個 Index index1 = 2; // 第5個 Index index2 = 5; // 倒數(shù)第1個 Index index3 = ^1; // 倒數(shù)第3個 Index index4 = ^3; // 輸出:55 Console.WriteLine(fibSequence[index3]); // 輸出:21 Console.WriteLine(fibSequence[index4]);
可以看出 ^n 實(shí)際上等同于 sequence.Length – n,因?yàn)?span id="98gwtlm" class="wpcom_tag_link">數(shù)組的最后一個元素的索引是 sequence.Length – 1,因此 n = 0 時會報錯,即 ^0 訪問單個元素時會報錯。
嗯,現(xiàn)在感覺有點(diǎn)意思了,但是不是有點(diǎn)繁瑣呢,還要申明一個 Index 類型的變量才能用。其實(shí)不用,我們之所以寫出來,是因?yàn)閺?qiáng)調(diào)其背后的數(shù)據(jù)類型為新引入的 System.Index,實(shí)際中直接使用字面量即可。
int[] fibSequence = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 }; // 倒數(shù)第1個:55 Console.WriteLine(fibSequence[^1]); // 倒數(shù)第2個:21 Console.WriteLine(fibSequence[^2]); // 倒數(shù)第0個:報錯 Console.WriteLine(fibSequence[^0]);
范圍
C# 8 引入了新類型 System.Range 表示序列的一部分,表達(dá)形式為 起始索引..結(jié)束索引。
int[] fibSequence = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 }; // {2,3} var subSequence1 = fibSequence[2..4]; // {3,5} var subSequence2 = fibSequence[3..5]; // { 1, 1, 2, 3, 5, 8, 13, 21, 34 } var subSequence3 = fibSequence[0..^1];
可以看出,結(jié)束索引不包括在結(jié)果中。2..4 實(shí)際上包含 fibSequence[2] 到 fibSequence[3]的值。以此類推, 0..^1 包含fibSequence[0] 到 fibSequence[^2]的值, 缺少最后一個元素。要表示整個范圍,可以用 0..^0 。
開始索引和結(jié)束索引可以省略。
int[] fibSequence = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 }; // 等同 5..^0 :{ 8, 13, 21, 34, 55 } var subSequence1 = fibSequence[5..]; // 等同 0..^5 : { 1, 1, 2, 3, 5 } var subSequence2 = fibSequence[..5]; // 全部,等同于 0..^0 var subSequence3 = fibSequence[..];
### 類型支持
索引和范圍的支持不限于數(shù)組,方式包括顯示支持和隱式支持。
顯式支持
通過實(shí)現(xiàn)帶 Index 或者 Range 類型參數(shù)的索引器來顯式支持索引或范圍。
public class ExplicitIndexAndRange { private List _list = new(); /// /// 顯示索引,通過帶 Index 類型參數(shù)的索引器實(shí)現(xiàn) /// public int this[Index index] { get { var idx = index.IsFromEnd ? _list.Count – index.Value : index.Value; if (idx = _list.Count) { return -1; } return _list[idx]; } } /// /// 顯示支持范圍,通過帶 Range 類型參數(shù)的索引器實(shí)現(xiàn) /// public List this[Range range] { get { var sIdx = range.Start; var eIdx = range.End; var startIndex = sIdx.IsFromEnd ? _list.Count – sIdx.Value : sIdx.Value; var endIndex = eIdx.IsFromEnd ? _list.Count – eIdx.Value : eIdx.Value; var newList = new List(); for (int i = startIndex; i < endIndex; i++) { newList.Add(_list[i]); } return newList; } } }
隱式支持索引
滿足以下條件,編譯器自動添加索引支持。
- 具有一個返回 int 值的 Length 或者 Count 屬性
- 具有可訪問的實(shí)例索引器,該索引器采用單個 int 作為參數(shù)
- 沒有顯式實(shí)現(xiàn)索引器
public class ImplicitIndexAndRange { private List _list = new(); /// /// 實(shí)現(xiàn) Length 或者 Count 屬性,必須返回 int 類型值 /// public int Count => _list.Count; /// /// 實(shí)現(xiàn) int 索引器 /// public int this[int index] { get { if (index = Count) { return -1; } return _list[index]; } } }
Range 隱式支持
滿足以下條件,編譯器自動添加索引支持。
- 具有一個返回 int 值的 Length 或者 Count 屬性
- 具有可訪問方法 Slice ,它具有兩個類型為 int 的參數(shù)
- 沒有顯式實(shí)現(xiàn)范圍
public class ImplicitIndexAndRange { private List _list = new(); /// /// 實(shí)現(xiàn) Length 或者 Count 屬性,必須返回 int 類型值 /// public int Count => _list.Count; /// /// Slice,省略了長度檢查異常處理 /// public int[] Slice(int start, int length) { var slice = new int[length]; for (var i = start; i < start + length; i++) { slice[i] = _list[i]; } return slice; } }