概要
本文主要分析Linq中Take帶Range參數的重載方法的源碼。
源碼分析
基于Range參數的Take重載方法,主要分成兩部分實現,一部分是Range中的開始和結束索引都是正數的情況例如取第一個到第三個元素的情況;另一部分是開始或結束索引中有倒數的情況,例如取倒數第三個到倒數第一個的情況。
本文著重分析Range中的正數情況。
public static IEnumerable<TSource> Take<TSource>(this IEnumerable<TSource> source, Range range)
{if (source == null){ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);}Index start = range.Start;Index end = range.End;bool isStartIndexFromEnd = start.IsFromEnd;bool isEndIndexFromEnd = end.IsFromEnd;int startIndex = start.Value;int endIndex = end.Value;Debug.Assert(startIndex >= 0);Debug.Assert(endIndex >= 0);if (isStartIndexFromEnd){if (startIndex == 0 || (isEndIndexFromEnd && endIndex >= startIndex)){return Empty<TSource>();}}else if (!isEndIndexFromEnd){return startIndex >= endIndex? Empty<TSource>(): TakeRangeIterator(source, startIndex, endIndex);}return TakeRangeFromEndIterator(source, isStartIndexFromEnd, startIndex, isEndIndexFromEnd, endIndex);
}
- 檢查源序列是否為空,如果為空,直接拋出異常;
- 獲取Range的啟始和結束的索引值,以及索引值是正數還是倒數的bool值;
- 如果開始索引值是倒數,以下幾種情況返回空序列:
(a)開始索引是^0, 倒數第0個,顯然不合理
(b)Range形如 ^1… ^3的情況,假設有10個元素, ^1… ^3相當于取從第10個到第7個,顯然是不合理。應該是從第7個到第10個
(c)Range形如 ^2 … ^2因為開始和結束索引相同,中間沒有間隔元素,該種情況也不合理 - 在Range中的開始和結束索引都不是倒數的情況下,如果開始索引大于結束索引,即Range形如2…1,返回空序列;否則調用TakeRangeIterator方法,完成具體取值操作;
- 對于合理的Range倒數情況,例如形如 ^3… ^1 , 3… ^1 或 ^3 … 10 這些情況,執行最后的TakeRangeFromEndIterator方法。
TakeRangeIterator方法
TakeRangeIterator方法用于處理Range中的開始和結束索引都是正數的情況。該方法位于Take.SizeOpt文件中。通過yield return/break的方式管理迭代過程。
private static IEnumerable<TSource> TakeRangeIterator<TSource>(IEnumerable<TSource> source, int startIndex, int endIndex)
{Debug.Assert(source != null);Debug.Assert(startIndex >= 0 && startIndex < endIndex);using IEnumerator<TSource> e = source.GetEnumerator();int index = 0;while (index < startIndex && e.MoveNext()){++index;}if (index < startIndex){yield break;}while (index < endIndex && e.MoveNext()){yield return e.Current;++index;}
}
- 創建迭代器e,采用using方式,在函數執行完成后,自動釋放內存空間;
- 如果Range中的索引數據和source序列中的元素個數不匹配,例如指定從第三個元素開始取,但是數列里面只有兩個元素,返回yield break,關閉狀態機,注意,此種情況并不會拋出越界異常;
- 按照索引范圍,通過迭代器e取值,創建狀態機,通過yield return方式返回。
TakeRangeFromEndIterator方法
TakeRangeIterator方法用于處理Range中的開始和結束索引存在倒數的情況。該方法位于Take.cs文件中。通過yield return/break的方式管理迭代過程。
該方法篇幅較長,將在C# Linq源碼分析之Take (三)中詳細分析其源碼。