泛型 List<T>.Contains() 中的值与引用相等性

Value vs. Reference equality in generic List<T>.Contains()

尝试 #3 来简化这个问题:

泛型 List<T> 可以包含任何类型 - 值或引用。在检查列表是否包含对象时,.Contains() 使用类型 T 的默认值 EqualityComparer<T>,并调用 .Equals()(我的理解)。如果没有定义 EqualityComparer,默认比较器将调用 .Equals()。默认情况下,.Equals() 调用 .ReferenceEquals(),因此如果列表包含完全相同的对象,.Contains() 只会 return 为真。

直到您需要重写 .Equals() 来实现值相等,此时默认比较器说如果两个对象具有相同的值则它们是相同的。我想不出一个案例需要引用类型。

我从@Enigmativity 那里听到的是,实施 IEqualityComparer<StagingDataRow> 将为我键入的 DataRow 提供一个默认的相等比较器,该比较器将用于代替 Object 的默认比较器——允许我实施StagingDataRow.Equals().

中的值相等逻辑

问题:

  1. 我理解正确吗?
  2. 我能保证 .NET 框架中的所有内容都会调用 EqualityComparer<StagingDataRow>.Equals() 而不是 StagingDataRow.Equals() 吗?
  3. IEqualityComparer<StagingDataRow>.GetHashCode(StagingDataRow obj) 应该针对什么进行哈希运算,它 return 应该与 StagingDataRow.GetHashCode() 的值相同吗?
  4. 传递给 IEqualityComparer<StagingDataRow>.GetHashCode(StagingDataRow obj) 的是什么?我正在寻找的对象还是列表中的对象?两个都?让实例方法接受自己作为参数会很奇怪...

一般来说,重写时如何区分值相等和引用相等?.Equals()



引发此问题的原始代码行:

//  For each ID, a collection of matching rows
Dictionary<string, List<StagingDataRow>> stagingTableDictionary;


StagingTableMatches.AddRange(stagingTableDictionary[perNr].Where(row => !StagingTableMatches.Contains(row)));

.

Am I understanding that correctly?

部分 - "default" IEqualityComparer 将使用(按顺序):

  1. 执行IEquatable<T>
  2. 已覆盖 Equals(object)
  3. 基础object.Equals(object),这是引用类型的引用相等性。

我认为您混淆了在自定义类型中定义 "equality" 的两种不同方法。一种是通过实现 IEquatable<T>,它允许一个类型的实例确定它是否是 "equal" 到另一个相同类型的实例。

另一个是IEqualityComparer<T>,它是一个独立接口,用于确定两个该类型的实例是否相等。

因此,如果您对 Equals 的定义应在您比较两个实例时应用 ,则实施 IEquatable,并覆盖 Equals(在实施 IEquatable 之后通常是微不足道的)和 GetHashCode.

如果您对 "equal" 的定义仅适用于特定用例,则创建一个 不同的 class 实现 IEqualityComparer<T>,然后将其实例传递给任何 class 或您希望 应用 定义的方法。

Am I guaranteed that everything in the .NET framework will call EqualityComparer<StagingDataRow>.Equals() instead of StagingDataRow.Equals()?

否 - 只有接受 IEqualityComparer 实例作为参数的类型和方法才会使用它。

What should IEqualityComparer<StagingDataRow>.GetHashCode(StagingDataRow obj) hash against, and should it return the same value as StagingDataRow.GetHashCode()?

它将计算传入对象的散列码。它不会 "compare" 散列码到任何东西。它不一定 return 与覆盖的 GetHashCode 相同的值,但它 必须 遵循 GetHashCode 的规则,特别是两个 "equal" 对象必须 return 相同的哈希码。

It would be strange to have an instance method accept itself as a parameter...

这就是为什么 IEqualityComparer 通常在 不同的 class 上实现的原因。请注意 IEquatable<T> 没有 GetHashCode() 方法,因为 它不需要 。它假设 GetHashCode 被覆盖以匹配 object.Equals 的覆盖,这应该匹配 IEquatable<T>

的强类型实现

底线

如果您希望 "equal" 的定义成为该类型的默认值,请实施 IEquatable<T> 并覆盖 EqualsGetHashCode。如果你想要一个只针对特定用例的 "equal" 的定义,那么创建一个 不同的 class 来实现 IEqualityComparer<T> 并传递一个实例将其分配给需要使用该定义的任何类型或方法。

此外,我会注意到您很少直接调用这些方法(Equals 除外)。它们通常由使用它们的方法调用(如 Contains)以确定两个对象是否 "equal" 或获取项目的哈希码。

好吧,我们先来纠正几个误区:

By default, .Equals() calls .ReferenceEquals(), so .Contains() will only return true if the list contains the exact same object.

这是真的,但仅适用于引用类型。默认情况下,值类型将实现一个 very slow reflection-based Equals 函数,因此最好覆盖它。

I can't think of a single case where that would be desirable for a reference type.

哦,我相信你可以... String 例如是引用类型:)

What I'm hearing from @Enigmativity is that implementing IEqualityComparer<StagingDataRow> will give my typed DataRow a default equality comparer that will be used instead of the default comparer for Object – allowing me to implement value equality logic in StagingDataRow.Equals().

错误...不。

IEqualityComaprer<T> 是一个接口,可让您将相等性比较委托给 different 对象。如果您希望 class 有不同的 default 行为,您可以实现 IEquatable<T>,并为了一致性将 object.Equals 委托给它。实际上,重写 object.Equalsobject.GetHashCode 足以 改变默认的相等比较行为,而且实施 IEquatable<T> 有额外的好处:

  • 它更明显地表明您的类型具有自定义相等比较逻辑 - 想想自我记录代码。
  • 它提高了值类型的性能,因为它避免了不必要的装箱(发生在 object.Equals 中)

因此,对于您的实际问题:

Am I understanding that correctly?

您对此似乎还有点困惑,但别担心:)

Enigmativity 实际上建议您创建一个 不同的 类型来实现 IEqualityComparer<T>。看来你误解了那部分。

Am I guaranteed that everything in the .NET framework will call EqualityComparer<StagingDataRow>.Equals() instead of StagingDataRow.Equals()

默认情况下,(正确编写的)框架数据结构会将相等比较委托给 EqualityComparer<StagingDataRow>.Default,后者又会委托给 StagingDataRow.Equals

What should IEqualityComparer<StagingDataRow>.GetHashCode(StagingDataRow obj) hash against, and should it return the same value as StagingDataRow.GetHashCode()

不一定。它应该是自洽的:如果 myEqualitycomaprer.Equals(a, b) 那么你 必须 确保 myEqualitycomaprer.GetHashCode(a) == myEqualitycomaprer.GetHashCode(b).

可以StagingDataRow.GetHashCode实现相同,但不一定。

What is passed to IEqualityComparer<StagingDataRow>.GetHashCode(StagingDataRow obj)? The object I'm looking for or the object in the list? Both? It would be strange to have an instance method accept itself as a parameter...

好吧,现在我希望你已经明白实现 IEqualityComparer<T> 的对象是一个 不同的 对象,所以这应该是有道理的。


请阅读我在 Using of IEqualityComparer interface and EqualityComparer class in C# 上的回答以获得更深入的信息。