上一章我們講到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組別取得對應的資料從取值的方式可以知道其中一方沒有值是都不會成為結果的。
[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。