今天要來說說Take的原始碼,由於Take跟Skip非常的相似,所以有些部分在Skip已經說過了,在這裡就只會帶過,不會再深入的說明,這裡建議可以先回去看Skip的部分再來看本篇文章。
Source Code: Take.cs
前面有提到Take有三個不同名稱的方法: Take、TakeLast、TakeWhile,下面我們依序來做說明。
方法的實作如下:
public static IEnumerable<TSource> Take<TSource>(this IEnumerable<TSource> source, int count)
{
...
if (source is IPartition<TSource> partition)
{
return partition.Take(count);
}
if (source is IList<TSource> sourceList)
{
return new ListPartition<TSource>(sourceList, 0, count - 1);
}
return new EnumerablePartition<TSource>(source, 0, count - 1);
}
前面檢查傳入參數是否合法的部分用...代替,剩下的是真正做事的地方,這裡會做下面幾件事:
IPartition的類別,直接叫用之前設定的Take
IList,叫用ListPartition
EnumerablePartition
這裡我們把焦點放到Skip中有出現的ListPartition跟EnumerablePartition上,它們傳入的index跟Skip剛好相反:
minIndexInclusive: 設為0,代表從第一個元素開始取maxIndexInclusive: 設為count - 1,因為要從索引值0開始取,所以最後一個元素應該要count - 1
這邊看出Skip跟Take的互補關係,Take從0到count - 1,Skip從count到最後個元素,所以兩個合併就會是原本的集合。
TakeLast的方法有做下面幾件事:
ArgumentNull例外TakeLastIterator
接下來我們來看一下TakeLastIterator:
private static IEnumerable<TSource> TakeLastIterator<TSource>(IEnumerable<TSource> source, int count)
{
...
Queue<TSource> queue;
using (IEnumerator<TSource> e = source.GetEnumerator())
{
if (!e.MoveNext())
{
yield break;
}
queue = new Queue<TSource>();
queue.Enqueue(e.Current);
while (e.MoveNext())
{
if (queue.Count < count)
{
queue.Enqueue(e.Current);
}
else
{
do
{
queue.Dequeue();
queue.Enqueue(e.Current);
}
while (e.MoveNext());
break;
}
}
}
...
do
{
yield return queue.Dequeue();
}
while (queue.Count > 0);
}
這段程式我們可以知道:
count的數量這裡跟Skip同樣都是用Queue來實作,可是用法卻差很多,Skip不回傳Queue中的元素,反之Take卻是只回傳Queue中的元素。
TakeWhile的公開方法做了下面的事情:
ArgumentNull的例外TakeWhileIterator
接下來我們來看看TakeWhileIterator,他有兩個方法,差別在於predicate有沒有傳入index參數,我們直接來看有index參數的方法:
private static IEnumerable<TSource> TakeWhileIterator<TSource>(IEnumerable<TSource> source, Func<TSource, int, bool> predicate)
{
int index = -1;
foreach (TSource element in source)
{
checked
{
index++;
}
if (!predicate(element, index))
{
break;
}
yield return element;
}
}
index就加1
predicate的判斷,就回傳這個元素predicate的元素,則直接結束巡覽
Source Code: TakeTests.cs、TakeLastTests.cs、TakeWhileTests.cs
[Fact]
public void SourceNonEmptyPredicateTrueSomeFalseSecond()
{
int[] source = { 8, 3, 12, 4, 6, 10 };
int[] expected = { 8 };
Assert.Equal(expected, source.TakeWhile(x => x % 2 == 0));
}
只要遇到不符合predicate的元素,之後的元素都不再做判斷直接忽略。
這次觀看的Take中跟Skip的相似之處有很多,但是也有不同且特別的部分,從Skip跟Take的原始碼中我覺得收穫最多的就是Queue的運用,利用Queue暫緩回傳時機,可以使回傳的資料有更多的彈性。