[英]Need help understanding unexpected behavior using LINQ Join with HashSet<T>
使用C#HastSet和我不理解的LINQ的Join方法時,遇到了一些奇怪的行為。 我簡化了我的工作,以幫助專注於自己所看到的行為。
我有以下幾點:
private HashSet<MyClass> _mySet; // module level
IEnumerable<ISearchKey> searchKeys; // parameter.
// Partial key searches are allowed.
private IEqualityComparer<ICoreKey> _coreKeyComparer; // Module level.
// Compares instances of MyClass and ISearchKey to determine
// if they match.
{searchKey_a, myClass_a1},
{searchKey_a, myClass_a2},
{searchKey_a, myClass_a3},
{searchKey_b, myClass_b1},
{searchKey_b, myClass_b2},
{searchKey_c, myClass_c1},
{searchKey_c, myClass_c2},
{searchKey_c, myClass_c3},
{searchKey_c, myClass_c4},
etc....
即同一個ISearchKey實例將發生多次,對於它加入的每個匹配的MyClass實例一次。
var matchedPairs = searchKeys
.Join(
_mySet,
searchKey => searchKey,
myClass => myClass,
(searchKey, myClass) => new {searchKey, myClass},
_coreKeyComparer)
.ToList();
每個searchKeyClass實例只有一個MyClass實例。 即matchedPairs集合看起來像:
{searchKey_a, myClass_a1},
{searchKey_b, myClass_b1},
{searchKey_c, myClass_c1},
etc....
var matchedPairs = _mySet
.Join(
searchKeys,
myClass => myClass,
searchKey => searchKey,
(myClass, searchKey) => new {searchKey, myClass},
_coreKeyComparer)
.ToList();
我得到正確的matchPairs集合。 _mySet中的所有匹配記錄都將與它們匹配的searchKey一起返回。
我檢查了文檔並檢查了多個示例,沒有看到為什么searchKeys-to_mySet連接給出錯誤答案的原因,而_mySet-to-searchKeys提供正確/不同答案的原因。
(旁注:我還嘗試了將groupJoin從searchKeys到_myset並進行相似的結果。即,每個searchKeyClass實例最多可以從_mySet找到一個結果。)
我要么不明白Join方法應該如何工作,要么Join與HashSet的工作方式與List或其他類型的集合不同。
如果是前者,我需要澄清一下,這樣以后在使用Join時就不會犯錯誤。
如果是后者,則此不同行為是.NET錯誤,還是HashSet的正確行為?
假定行為正確,我將不勝感激有人解釋了此(意外)Join / HashSet行為背后的基本邏輯。
為了清楚起見,我已經修復了我的代碼,以便它返回正確的結果,我只想了解為什么最初得到錯誤的結果。
您的錯誤幾乎可以肯定地存在於問題中沒有顯示的大量代碼中。 我的建議是將您的程序簡化為可能產生該錯誤的最簡單程序 。 這樣一來,您就可以找到錯誤,或者生成一個非常簡單的程序,可以將所有問題發布到問題中,然后我們可以對其進行分析。
假定行為正確,我將不勝感激有人解釋了此(意外)Join / HashSet行為背后的基本邏輯。
由於我不知道意外行為是什么,所以我無法說出它為什么發生。 但是,我可以准確地說出Join
作用,也許會有所幫助。
Join
需要以下內容:
Join
。 這是Join
工作方式。 (這在邏輯上是會發生的;實際的實現細節已得到一些優化。)
首先,我們僅對“內部”集合進行一次迭代。
對於內部集合的每個元素,我們提取其鍵,然后形成一個多字典,該字典將鍵映射到內部集合中所有鍵的集合,鍵選擇器在該集合中生成該鍵。 使用提供的比較對鍵進行相等性比較。
因此,我們現在從TKey
到IEnumerable<TInner>
進行查找。
其次,我們僅對一次“外部”集合進行迭代。
對於外部集合的每個元素,我們提取其鍵,並再次使用提供的鍵比較在多字典中對該鍵進行查找。
然后,我們在內部集合的每個匹配元素上進行嵌套循環,在外部/內部對上調用投影,然后得出結果。
也就是說, Join
行為類似於以下偽代碼實現:
static IEnumerable<TResult> Join<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)
{
var lookup = new SomeMultiDictionary<TKey, TInner>(comparer);
foreach(TInner innerItem in inner)
{
TKey innerKey = innerKeySelector(innerItem);
lookup.Add(innerItem, innerKey);
}
foreach (TOuter outerItem in outer)
{
TKey outerKey = outerKeySelector(outerItem);
foreach(TInner innerItem in lookup[outerKey])
{
TResult result = resultSelector(outerItem, innerItem);
yield return result;
}
}
}
一些建議:
GetHashCode
實現,使它們返回0
,然后運行所有測試。 他們應該通過! 從GetHashCode
返回零始終是合法的。 這樣做幾乎肯定會破壞您的表現 ,但絕對不能破壞您的正確性 。 如果您需要 GetHashCode
的特定非零值,那么您將遇到一個錯誤。 A
和B
的相等性必須與B
和A
相同;(3)可傳遞性:如果A
等於B
且B
等於C
那么A
必須等於C
如果不滿足這些規則,那么Join
可能會表現異常。 用SelectMany
和Where
代替Join
。 那是:
from o in outer join i in inner on getOuterKey(o) equals getInnerKey(i) select getResult(o, i)
可以改寫成
from o in outer
from i in inner
where keyEquality(getOuterKey(o), getInnerKey(i))
select getResult(o, i)
該查詢比連接版本慢 ,但從邏輯上講完全相同。 再次,運行測試。 你得到相同的結果嗎? 如果不是,則說明您的邏輯中存在錯誤 。
同樣,我不能足夠強調您的態度,即“給定哈希表時聯接可能被破壞”的態度使您無法找到錯誤。 加入沒有中斷。 該代碼十年來沒有發生變化,它非常簡單,並且在我們第一次編寫時是正確的。 您的復雜而神秘的鍵比較邏輯更有可能在某處被破壞了。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.