今天要來說說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
暫緩回傳時機,可以使回傳的資料有更多的彈性。