概要
本文主要分析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 (三)中详细分析其源码。