实现IEquatable<T>后LINQ Distinct方法无法正常去重的问题排查
实现IEquatable后LINQ Distinct方法无法正常去重的问题排查
嘿,我仔细看了你的代码,问题主要出在GetHashCode的实现上,另外Equals方法里还有个明显的笔误,咱们一个个说:
第一个坑:List的默认GetHashCode不看内容
你在计算哈希的时候用了StringBars.GetHashCode(),但List<T>作为引用类型,它的默认哈希值是基于对象引用的,不是集合里的元素内容。也就是说,哪怕两个List的元素完全一模一样,只要是不同的实例,它们的哈希值就会不一样。这直接导致两个本该被判定为相等的Bar对象,因为StringBars的哈希不同,整体哈希值也不一样,LINQ的Distinct自然认不出它们是同一个。
第二个坑:用异或(^)组合哈希值的缺陷
你把所有字段的哈希值用异或来合并,这是个常见的错误。异或有两个大问题:
- 顺序无关:比如
a ^ b和b ^ a结果完全一样,这意味着两个字段值交换但其他都相同的对象,会得到相同的哈希值,但它们实际是不相等的,容易引发哈希碰撞。 - 抵消问题:如果某个字段的哈希值出现偶数次,会直接抵消为0,比如
x ^ x = 0,这会导致本该不同的对象哈希值相同,或者本该相同的因为某些字段的哈希抵消而不同。
第三个坑:Equals方法里的笔误
你的Equals方法最后写了ContractType == other.ContractType && LotNumber == other.LotNumber && Rank == other.Rank,但你的Bar类里根本没有这三个属性!这要么会导致编译错误,要么是你复制粘贴时搞错了字段名——正确的应该是比较你的Bar类里实际存在的EnumFoo2、StringBar6和Foo吧?这个笔误会直接导致Equals逻辑错误,哪怕哈希值对了,Distinct也会因为Equals返回false而不合并对象。
解决方案
咱们一步步修正:
- 先修正Equals里的错误字段
把那些不存在的字段替换成你Bar类里实际有的属性,修正后的Equals应该是这样:
public bool Equals(Bar other) { if (other == null) { return false; } return Stringbar1 == other.Stringbar1 && Stringbar2 == other.Stringbar2 && Stringbar3 == other.Stringbar3 && Stringbar4 == other.Stringbar4 && EnumFoo1 == other.EnumFoo1 && IsBar == other.IsBar && BothNullOrEquals(StringBars, other.StringBars) && Stringbar5 == other.Stringbar5 && FooDate == other.FooDate && EnumFoo2 == other.EnumFoo2 && StringBar6 == other.StringBar6 && Foo == other.Foo; }
- 写一个辅助方法计算集合的内容哈希
用来替代List默认的引用哈希,确保相同内容的集合得到相同的哈希值:
private static int GetEnumerableHashCode<T>(IEnumerable<T> enumerable) { if (enumerable == null) return 0; int hash = 17; // 起始质数 foreach (var item in enumerable) { // 用质数乘法累加,减少碰撞概率 hash = hash * 23 + (item?.GetHashCode() ?? 0); } return hash; }
- 重写GetHashCode方法
用质数乘法累加的方式替代异或,同时用上上面的集合哈希辅助方法:
public override int GetHashCode() { int hash = 17; hash = hash * 23 + (Stringbar1?.GetHashCode() ?? 0); hash = hash * 23 + (Stringbar2?.GetHashCode() ?? 0); hash = hash * 23 + (Stringbar3?.GetHashCode() ?? 0); hash = hash * 23 + EnumFoo1.GetHashCode(); hash = hash * 23 + (Stringbar4?.GetHashCode() ?? 0); hash = hash * 23 + IsBar.GetHashCode(); hash = hash * 23 + GetEnumerableHashCode(StringBars); hash = hash * 23 + (Stringbar5?.GetHashCode() ?? 0); hash = hash * 23 + (FooDate?.GetHashCode() ?? 0); hash = hash * 23 + EnumFoo2.GetHashCode(); hash = hash * 23 + (StringBar6?.GetHashCode() ?? 0); hash = hash * 23 + Foo.GetHashCode(); return hash; }
这样修改后,你的GetHashCode就会和Equals逻辑保持一致,LINQ的Distinct方法就能正确识别相等的Bar对象,实现去重了。
备注:内容来源于stack exchange,提问作者Mohamad Hammash
相关产品推荐
相关产品推荐

