IEnumerable.Except() 如何运作?

How IEnumerable.Except() works?

我正在尝试排除要添加到数据库中的实体(如果它们已经存在于数据库中)。所以我决定 newBillInstances.Except(dbContext.BillInstances) 是最好的方法。然而,它根本不起作用(没有实体被排除在外),尽管 List<string> 它工作得很好。

我读了this discussion and actual decription of .Except() in MSDN。它声明要在 .Except() 中使用的 class 应该实现 IEqualityComparer<T> 以使用默认比较器。

其实MSDN的文章并没有完整的描述比较两个实例的过程。我仍然不明白为什么 Equals() 和 GetHashObject() 都必须被覆盖。

我已经实现了 IEqualityComparer<BillInstance> 接口并在两个方法中都设置了断点,但是在调用 .Except(IEnumerable) 时它没有被使用。只有当我更改为 .Except(IEnumerable, new BillInstanceComparer()) 时,我才在 GetHashCode() 中咳嗽,但在 Equals() 中没有咳嗽。

然后我在 BillInstance class 中实现了 IEqualityComparer<BillInstance> 并预计它会在使用 .Except(IEnumerable) 时使用,但两种方法都没有中断。

所以我有两个问题:

  1. 使用.Except(IEnumerable)需要做什么?
  2. 为什么根本不用 Equals()?是否只在两个实例的哈希码相同的情况下使用?

因为Equals()仅在两个对象具有相同的GetHashCode()时使用。如果没有对象具有相同的 GetHashCode() 那么就没有机会使用 Equals().

内部Except()使用Set<>(你可以看到它here), that is an internal class that you should consider to be equivalent to HashSet<>。这个class使用对象的散列来"index"它们,然后使用 Equals() 检查具有相同哈希值的两个对象是否相同或不同,但具有相同的哈希值。

Link 其他相关回答:

代码中某处隐藏了一个集合或一个map/dictionary。

这些家伙通常包含许多桶,这些桶随着集合中存储的元素数量而增长。一个元素根据哈希码被划分到桶中,桶内的实际身份比较是使用 equals 完成的。

因此哈希码用于查找正确的存储桶(为什么需要 GetHashCode),然后使用 equals 将其与存储桶中的其他元素进行比较。

这就是您需要同时实施两者的原因。

好的,从 IEnumerable 来源 () 我已经了解调用 Except(IEnumerable):

的内部原理
  1. enumerable1.Except(enumerable2) 调用 ExceptIterator(enumerable1, enumerable2, null),其中 null 应该是 IEquitableComparer 的实例。

  2. ExceptIterator() 创建一个内部实例 class Set 传递 null 作为比较器。

  3. 由于比较器是 null,因此使用 属性 EqualityComparer<TElement>.Default

  4. Default 属性 为 TElement 创建一个比较器,除非它已经通过调用 CreateComparer() 创建。具体有 2 点对我来说很有趣:

    • 如果 TElement 实现了 IEquatable 接口,那么据我所知,一些 IEquatable 的通用比较器被创建。我相信它会使用 IEquatable.GetHashCode()IEquatable.Equals().

    • 对于一般情况(不是字节类型,没有实现 IEquatable,不是 Nullable,不是枚举)返回 ObjectEqualityComparer 实例。 ObjectEqualityComparer.GetHashCode()ObjectEqualityComparer.Equals()一般调用TElement.

    • 对应的方法

所以这让我理解了我的情况(BillInstance 的每个实例通常是不可变的)它应该足以覆盖 Object.GetHashCode()Object.Equals().