概要
在.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值設定的是倒數方式,我們就需要知道序列中元素的個數,然后將倒數值變成正數值,最后后按照索引取值。
試想如果目標序列數據量很大,如果可以直接獲取序列中元素的個數,就可以避免逐個元數遍歷,再獲取序列中元素個數,可以實現優化,提高代碼運行速度。