概要
在.Net 6 中引入的Take的另一个重载方法,一个基于Range的重载方法。因为该方法中涉及了很多新的概念,所以在分析源码之前,先将这些概念搞清楚。
Take方法基本介绍
public static System.Collections.Generic.IEnumerable Take (this System.Collections.Generic.IEnumerable source, Range range);
新的Take重载方法不再接收整数参数,而是接收一个Range类型的参数。它可以从序列中返回指定范围的连续元素。
注意:该方法只能在.Net 6 或更高版本中使用,我们之前用的.Net 4.7, 4.8或.Net Core 3.1中,都不支持该方法。
Range参数的数据结构
Take重载方法的参数是Range类型,Range本身是一个struct, 表示具有起始索引和结束索引的范围。
Range有两个重要的属性Start和End,表示Range的启始索引值和结束索引值,在Take 方法中只会取到结束索引值的前一个元素。
End和Start的属性的类型值是Index,也是struct结构。包含两个重要属性,Value和IsFromEnd。
IsFromEnd是bool类型,表示是正数还是倒数,例如我们可以写成3…^1 表示从第三个到倒数第一个。
Value是int类型,表示索引值。无论是1 或^1, 索引值都是1。
Take 方法的基本使用
static void Main(string[] args)
{
Range range = 1..3;
List<string> list = new List<string> { "A", "B", "C", "D", "E", "F", "G" };
foreach(var val in list.Take(range))
{
Console.WriteLine(val);
}
}
输出结果是 B 和 C
C#中对Range的定义与Python类似。
C#中可以按照正数的方式设定Range,也可以按照倒数的方式设定,也可以按照正数和倒数的混合方式设定Range。
上述代码中,我们将Range定义为Range range = ^6…3;,也可以得到相同的结果。
但是需要注意的是,Take方法中的Range必须是有意义的,注意下面的情况:
- ^1…3, 代人Take将得到空序列,因为无论正数还是倒数,Range的开始值必须小于结尾的值, 在上面例子中 ^1就是6,相当于去从第6个到第3个元素,显然是不合理的,结果为空。
- 3…^4, 代人Take将得到空序列,该Range等价于取从第3个到第3个元素,结果是空。
- 无论正数还是倒数,都可以越界,我们写成0…100或 ^100… ^1,都能拿到序列的全部元素,不会抛出越界异常。
Take中使用TryGetNonEnumeratedCount方法实现优化
TryGetNonEnumeratedCount方法的基本定义如下:
public static bool TryGetNonEnumeratedCount (this System.Collections.Generic.IEnumerable source, out int count);
该方法用于在不遍历序列元素的情况的下,获取序列中元素个数。
如果目标序列source实现了 ICollection接口,则需要实现Count属性。因此该函数可以直接获取Count属值,并返回True,输出参数count是序列中的元素个数。
如果目标序列source没有实现ICollection接口,则无法直接获取元素个数,因此该函数返回False,count是0。
Take 在执行过程中,如果我们的Range值设定的是倒数方式,我们就需要知道序列中元素的个数,然后将倒数值变成正数值,最后后按照索引取值。
试想如果目标序列数据量很大,如果可以直接获取序列中元素的个数,就可以避免逐个元数遍历,再获取序列中元素个数,可以实现优化,提高代码运行速度。