今天要來看GroupJoin的內部實作,知道GroupJoin的使用方式後,應該不難猜出它的實作可能跟Join很相似: 因為GroupJoin主要還是做Join的動作,只是最後加了個resultSeletor讓它可以對每個鍵值做彙整,現在來看看是不是真的是這樣吧。
Source Code: GroupJoin.cs
如往常一樣,我們來看一下GroupJoin的公開方法,再一次的如同之前Join的原始碼,這裡只做了兩件事:
ArgumentNull例外GroupJoinIterator取得Iterator之後回傳其實在System.Linq專案中的公開方法都是這樣做處理的,只有做檢查參數的動作,接著就丟給其他Method做事,以後如果沒有其他特別的處理,都會像上面一樣用文字說明,就不再貼原始碼了。
接著我們來看一下GroupJoinIterator的實作:
private static IEnumerable<TResult> GroupJoinIterator<TOuter, TInner, TKey, TResult>(
IEnumerable<TOuter> outer,
IEnumerable<TInner> inner,
Func<TOuter, TKey> outerKeySelector,
Func<TInner, TKey> innerKeySelector,
Func<TOuter, IEnumerable<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);
do
{
TOuter item = e.Current;
yield return resultSelector(item, lookup[outerKeySelector(item)]);
}
while (e.MoveNext());
}
}
}
這邊可以看到跟JoinIterator很像的實作,差別在於下面兩點:
inner是否有值就叫傳入resultSelector作結果輸出inner再做一次迴圈,而是直接把整包inner丟給resultSelector
第一點是GroupJoin是Left Join的原因,在沒有inner資料的情況下還是會執行yield return,就算只有outer的資料也可以回傳。
第二點是GroupJoin可以彙整資料的原因,因為沒有做迴圈,所以丟給resultSelector的是inner的集合資料,所以可以用集合資料來做彙整。
[Fact]
public void OuterInnerBothSingleNullElement()
{
string[] outer = new string[] { null };
string[] inner = new string[] { null };
string[] expected = new string[] { null };
Assert.Equal(expected, outer.GroupJoin(inner, e => e, e => e, (x, y) => x, EqualityComparer<string>.Default));
Assert.Empty(outer.Join(inner, e => e, e => e, (x, y) => x, EqualityComparer<string>.Default));
}
這裡我們可以看到inner跟outer是有一個null的字串陣列,可是他還是會回傳有一個null的字串陣列,可是Join就會傳回空。
[Fact]
public void SelectorsReturnNull()
{
CustomerRec[] outer = new []
{
new CustomerRec{ name = "Tim", custID = null },
new CustomerRec{ name = "Bob", custID = null }
};
OrderRec[] inner = new []
{
new OrderRec{ orderID = 97865, custID = null, total = 25 },
new OrderRec{ orderID = 34390, custID = null, total = 19 }
};
JoinRec[] expected = new []
{
new JoinRec{ name = "Tim", orderID = new int?[]{ }, total = new int?[]{ } },
new JoinRec{ name = "Bob", orderID = new int?[]{ }, total = new int?[]{ } }
};
Assert.Equal(expected, outer.GroupJoin(inner, e => e.custID, e => e.custID, createJoinRec));
}
在鍵值為null的情形下會拿不到inner的資料,可是outer的資料依然可以輸出。
這次賞析的GroupJoin跟Join大同小異,最大的差別就在於GroupJoin可以不用inner的資料也可以輸出,這也是造成GroupJoin是Left Join的原因,另外也因為GroupJoin沒有再做迴圈巡覽inner就輸出給resultSeletor做處理,所以可以直接彙整每個鍵值的資料,跟Join在用途及情境上就有差異了。