Join的原碼探索

上一章我們講到Join的應用方式,在方法中設定innerouter及對應的鍵值就可以取得兩個資料(物件)合併的資料,現在我們來看看他是怎麼做到的吧。

原始碼分析

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的鍵值去innerGrouping中查找是否有相同的鍵值組別
  • 有的話將目前的outerinner傳給resultSelector取得結果回傳

這裡我們看到了一個熟悉的身影,就是上次介紹GroupBy的時候有講解的Lookup,它的功用是可以將相同鍵值的物件整理到同一個Grouping物件中,這裡它將inner分組,outer再使用它查找對應的鍵值,藉以取得對應的資料。

這裡也可以看出因為外層是巡覽outerouter找到inner後才依序輸出outerinner的資料,所以資料排序會是outer後才是inner

最後這段實作告訴我們Join這個方法確實是Inner Join的實作,原因可以從innerouter取值的方式知道:

  • inner依鍵值分組
  • outerinner組別取得對應的資料

從取值的方式可以知道其中一方沒有值是都不會成為結果的。

測試案例賞析

[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));
}

如果是nullnullJoin的話,還是會得到

結語

這篇的篇幅比較短,真正複雜的分組(Lookup)已經在介紹GroupBy的時候講解了,這裡就是利用Lookup來取得對應的資料,明天我們來介紹另一個Join: GroupJoin

參考