Join的原碼探索
上一章我們講到Join
的應用方式,在方法中設定inner
跟outer
及對應的鍵值就可以取得兩個資料(物件)合併的資料,現在我們來看看他是怎麼做到的吧。
原始碼分析
Source Code: Join.cs
Join
有兩個公開方法,差別在於其中一個多了一個Comparer
的參數,而這兩個公開方法的實作其實就只差在這個Comparer
有沒有傳入Iterator
而已,下面列出了他們的實作流程:
- 判斷傳入的參數是否為空,如果是空則拋出
ArgumentNull
例外 - 如果參數皆合法,則叫用
JoinIterator
取得Join的結果
接下來我們來看JoinIterator
:
private static IEnumerable<TResult> JoinIterator<TOuter, TInner, TKey, TResult>(IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, TInner, TResult> resultSelector, IEqualityComparer<TKey> comparer)
{
using (IEnumerator<TOuter> e = outer.GetEnumerator())
{
if (e.MoveNext())
{
Lookup<TKey, TInner> lookup = Lookup<TKey, TInner>.CreateForJoin(inner, innerKeySelector, comparer);
if (lookup.Count != 0)
{
do
{
TOuter item = e.Current;
Grouping<TKey, TInner> g = lookup.GetGrouping(outerKeySelector(item), create: false);
if (g != null)
{
int count = g._count;
TInner[] elements = g._elements;
for (int i = 0; i != count; ++i)
{
yield return resultSelector(item, elements[i]);
}
}
}
while (e.MoveNext());
}
}
}
}
此Iterator
的流程如下:
- 巡覽
outer
的每個元素 - 取得以
inner
鍵值分組的inner
元素的Grouping
- 用
outer
的鍵值去inner
的Grouping
中查找是否有相同的鍵值組別 - 有的話將目前的
outer
及inner
傳給resultSelector
取得結果回傳
這裡我們看到了一個熟悉的身影,就是上次介紹GroupBy
的時候有講解的Lookup
,它的功用是可以將相同鍵值的物件整理到同一個Grouping
物件中,這裡它將inner
分組,outer
再使用它查找對應的鍵值,藉以取得對應的資料。
這裡也可以看出因為外層是巡覽outer
,outer
找到inner
後才依序輸出outer
跟inner
的資料,所以資料排序會是outer
後才是inner
。
最後這段實作告訴我們Join
這個方法確實是Inner Join的實作,原因可以從inner
跟outer
取值的方式知道:
inner
依鍵值分組outer
依inner
組別取得對應的資料
從取值的方式可以知道其中一方沒有值是都不會成為結果的。
測試案例賞析
- Source Code: JoinTests
[Fact]
public void SelectorsReturnNull()
{
int?[] inner = { null, null, null };
int?[] outer = { null, null };
Assert.Empty(outer.Join(inner, e => e, e => e, (x, y) => x));
}
如果是null
跟null
做Join
的話,還是會得到空。
結語
這篇的篇幅比較短,真正複雜的分組(Lookup
)已經在介紹GroupBy
的時候講解了,這裡就是利用Lookup
來取得對應的資料,明天我們來介紹另一個Join
: GroupJoin
。