RB *_*son 0 c# linq join inner-join hashset
我使用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.
Run Code Online (Sandbox Code Playgroud)
{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....
Run Code Online (Sandbox Code Playgroud)
ie同一个ISearchKey实例会多次出现,一次为它所连接的每个匹配的MyClass实例.
var matchedPairs = searchKeys
.Join(
_mySet,
searchKey => searchKey,
myClass => myClass,
(searchKey, myClass) => new {searchKey, myClass},
_coreKeyComparer)
.ToList();
Run Code Online (Sandbox Code Playgroud)
我只为每个searchKeyClass实例获得一个MyClass实例.即matchedPairs集合看起来像:
{searchKey_a, myClass_a1},
{searchKey_b, myClass_b1},
{searchKey_c, myClass_c1},
etc....
Run Code Online (Sandbox Code Playgroud)
var matchedPairs = _mySet
.Join(
searchKeys,
myClass => myClass,
searchKey => searchKey,
(myClass, searchKey) => new {searchKey, myClass},
_coreKeyComparer)
.ToList();
Run Code Online (Sandbox Code Playgroud)
我得到了正确的matchedPairs集合.来自_mySet的所有匹配记录与它们匹配的searchKey一起返回.
我查看了文档并检查了多个示例,但没有看到为什么searchKeys-to-_mySet Join给出了错误的答案,而_mySet-to-searchKeys给出了正确/不同的答案.
(旁注:我也尝试了从searchKeys到_myset的GroupJoin并得到了类似的结果.即每个searchKeyClass实例最多找到一个来自_mySet的结果.)
我不明白Join方法应该如何工作,或者Join与HashSet的工作方式不同于List或其他类型的集合.
如果是前者,我需要澄清,所以我不会在将来使用Join时犯错误.
如果是后者,那么这个不同的行为是一个.Net bug,或者这是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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
一些建议:
GetHashCode实现以便它们返回0,并运行所有测试.他们应该通过!从中返回零总是合法的GetHashCode.这样做几乎肯定会破坏你的表现,但绝不能破坏你的正确性.如果您处于需要特定非零值的情况GetHashCode,那么您就有一个错误.A和B必须相等,B和A(3)传递性:如果A等于B和B等于C那么A必须相等C.如果不满足这些规则,那么Join可能表现得很奇怪.Join用a SelectMany和a 替换你的Where.那是:
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)
Run Code Online (Sandbox Code Playgroud)
该查询比连接版本慢,但它在逻辑上完全相同.再次,运行您的测试.你得到相同的结果吗?如果没有,你的逻辑中有一个错误.
同样,我不能强烈强调你的态度"加入可能在给出哈希表时被打破"是阻止你找到你的bug的原因.加入不破.这个代码在十年内没有改变,它非常简单,当我们第一次写它时它是正确的.更有可能的是,你的复杂而神秘的关键比较逻辑在某处被打破.