通用List中的值与引用相等<t> .Contains()

时间:2016-05-15 21:11:00

标签: c# linq gethashcode iequalitycomparer iequatable

尝试#3简化此问题:

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

在您需要覆盖.Equals()以实现值相等之前,默认比较器表示如果两个对象具有相同的值,则它们是相同的。我想不出一个单一的案例,那里的参考类型是可取的。

我从@Enigmativity听到的是,实现IEqualityComparer<StagingDataRow>将为我的类型化DataRow提供一个默认的相等比较器,它将被用来代替Object的默认比较器 - 允许我实现值相等StagingDataRow.Equals()中的逻辑。

问题:

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

    通常,在覆盖.Equals()时,如何将一个值与引用相等分开?


    原始的代码行引发了这个问题:

    //  For each ID, a collection of matching rows
    Dictionary<string, List<StagingDataRow>> stagingTableDictionary;
    
    
    StagingTableMatches.AddRange(stagingTableDictionary[perNr].Where(row => !StagingTableMatches.Contains(row)));
    
    .
    


2 个答案:

答案 0 :(得分:2)

  

我理解正确吗?

部分 - “默认”IEqualityComparer将使用(按顺序):

  1. IEquatable<T>
  2. 的实施
  3. 被覆盖的Equals(object)
  4. 基础object.Equals(object),它是引用类型的引用相等。
  5. 我认为你混淆了两种在自定义类型中定义“相等”的方法。一种是通过实现IEquatable<T>,它允许类型的实例确定它是否与同一类型的另一个实例“相等”。

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

    因此,如果您在比较两个实例时定义Equals应该应用,那么请实施IEquatable,以及覆盖Equals(通常是实施IEquatable)和GetHashCode之后的微不足道。

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

      

    我保证.NET框架中的所有内容都会调用EqualityComparer<StagingDataRow>.Equals()而不是StagingDataRow.Equals()吗?

    否 - 仅接受IEqualityComparer实例作为参数的类型和方法将使用它。

      

    IEqualityComparer<StagingDataRow>.GetHashCode(StagingDataRow obj)应该如何散列,是否应该返回与StagingDataRow.GetHashCode()相同的值?

    它将计算传入的对象的哈希码。它不会将哈希码“比较”为任何东西。它不一定必须返回与被覆盖的GetHashCode相同的值,但必须遵循GetHashCode的规则,特别是两个“相等”对象必须返回相同的值哈希码。

      

    将实例方法接受为参数...

    会很奇怪

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

    底线

    如果您希望“等于”的定义为该类型的默认值,请实施IEquatable<T>并覆盖EqualsGetHashCode。如果你想要一个仅用于特定用例的“相等”的定义,那么创建一个实现IEqualityComparer<T>不同的类,并将它的实例传递给任何需要的类型或方法使用那个定义。

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

答案 1 :(得分:1)

好的,让我们先处理一些误解:

  

默认情况下,.Equals()会调用.ReferenceEquals(),因此如果列表包含完全相同的对象,.Contains()将仅返回true。

这是事实,但仅适用于参考类型。值类型默认会实现very slow reflection-based Equals函数,因此最好覆盖它。

  

我无法想到一个单一的案例,那里的参考类型是可取的。

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

  

我从@Enigmativity听到的是,实现IEqualityComparer<StagingDataRow>将为我的类型DataRow提供一个默认的相等比较器,而不是Object的默认比较器 - 允许我在StagingDataRow.Equals()中实现值相等逻辑。

错误......不。

IEqualityComaprer<T>是一个接口,允许您将相等比较委托给不同的对象。如果您希望为您的类提供不同的默认行为,请实施IEquatable<T>,并将object.Equals委托给它以保持一致性。实际上,覆盖object.Equalsobject.GetHashCode 足以更改默认的相等比较行为,但实施IEquatable<T>还有其他好处:

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

所以,对于你的实际问题:

  

我理解正确吗?

你似乎对此有点困惑,但不要担心:)

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

  

我保证.NET框架中的所有内容都会调用EqualityComparer<StagingDataRow>.Equals()而不是StagingDataRow.Equals()

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

  

IEqualityComparer<StagingDataRow>.GetHashCode(StagingDataRow obj)应该对哪些内容进行哈希处理,并且应该返回与StagingDataRow.GetHashCode()相同的值

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

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

  

传递给IEqualityComparer<StagingDataRow>.GetHashCode(StagingDataRow obj)的内容是什么?我正在寻找的对象或列表中的对象?都?让一个实例方法接受自己作为参数......会很奇怪。

好吧,到现在为止,我希望您已经了解实现IEqualityComparer<T>的对象是不同的对象,所以这应该是有意义的。

请阅读Using of IEqualityComparer interface and EqualityComparer class in C#上的答案,了解更多深入信息。