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。