在 C# 8 之前,要实现诸如“访问序列的倒数第二个元素””获取由第三个至第五个元素之间的元素组成的新序列”之类的功能是比较麻烦的,不够简洁直观。对此,C# 8 引入了索引和范围概念,极大简化了此类操作。
索引
C# 8 引入了新类型 System.Index 用于表示序列的索引,可以直接由 int 类型自动转换赋值。
int[] fibSequence = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 }; // 代表第3个元素的索引,起始索引为 0 Index index1 = 2; // 输出: 2 Console.WriteLine(fib)
看到这儿,性急的小伙伴应该要骂娘了,这不是多此一举嘛!直接用数字索引访问不是更好?
别着急,慢慢来,我们还可以在索引前面加 ^ 从后往前索引。
int[] fibSequence = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 }; // 第3个 Index index1 = 2; // 第5个 Index index2 = 5; // 倒数第1个 Index index3 = ^1; // 倒数第3个 Index index4 = ^3; // 输出:55 Console.WriteLine(fibSequence[index3]); // 输出:21 Console.WriteLine(fibSequence[index4]);
可以看出 ^n 实际上等同于 sequence.Length – n,因为数组的最后一个元素的索引是 sequence.Length – 1,因此 n = 0 时会报错,即 ^0 访问单个元素时会报错。
嗯,现在感觉有点意思了,但是不是有点繁琐呢,还要申明一个 Index 类型的变量才能用。其实不用,我们之所以写出来,是因为强调其背后的数据类型为新引入的 System.Index,实际中直接使用字面量即可。
int[] fibSequence = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 }; // 倒数第1个:55 Console.WriteLine(fibSequence[^1]); // 倒数第2个:21 Console.WriteLine(fibSequence[^2]); // 倒数第0个:报错 Console.WriteLine(fibSequence[^0]);
范围
C# 8 引入了新类型 System.Range 表示序列的一部分,表达形式为 起始索引..结束索引。
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];
可以看出,结束索引不包括在结果中。2..4 实际上包含 fibSequence[2] 到 fibSequence[3]的值。以此类推, 0..^1 包含fibSequence[0] 到 fibSequence[^2]的值, 缺少最后一个元素。要表示整个范围,可以用 0..^0 。
开始索引和结束索引可以省略。
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[..];
### 类型支持
索引和范围的支持不限于数组,方式包括显示支持和隐式支持。
显式支持
通过实现带 Index 或者 Range 类型参数的索引器来显式支持索引或范围。
public class ExplicitIndexAndRange { private List _list = new(); /// /// 显示索引,通过带 Index 类型参数的索引器实现 /// 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 类型参数的索引器实现 /// 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 属性
- 具有可访问的实例索引器,该索引器采用单个 int 作为参数
- 没有显式实现索引器
public class ImplicitIndexAndRange { private List _list = new(); /// /// 实现 Length 或者 Count 属性,必须返回 int 类型值 /// public int Count => _list.Count; /// /// 实现 int 索引器 /// public int this[int index] { get { if (index = Count) { return -1; } return _list[index]; } } }
Range 隐式支持
满足以下条件,编译器自动添加索引支持。
- 具有一个返回 int 值的 Length 或者 Count 属性
- 具有可访问方法 Slice ,它具有两个类型为 int 的参数
- 没有显式实现范围
public class ImplicitIndexAndRange { private List _list = new(); /// /// 实现 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; } }