首页 > 解决方案 > 列表中相同的对象会产生不同的哈希值并且无法通过比较测试

问题描述

我有一个奇怪的问题。我想使用一个函数来实现对 List 的扩展,以将另一个列表合并到其中,不包括重复值:

    <Extension()>
    Public Sub AddUnique(Of T)(ByVal self As IList(Of T), ByVal items As IEnumerable(Of T))
        For Each item In items
            If Not self.Contains(item) Then self.Add(item)
        Next
    End Sub

现在,我有一个类,我将从中创建对象,并将它们添加到列表中:

    Class DocInfo
        Public Property title As String
        Public Property fullPath As String

        Sub New(title As String, fullPath As String)
            Me.title = title
            Me.fullPath = fullPath
        End Sub
    End Class

然后,我有一个列表作为全局变量:

Public docsInfo As New List(Of DocInfo)

然后我有一个按钮处理程序,可以将新项目添加到该列表中:

Private Sub AddToList_Button_Click(sender As Object, e As RoutedEventArgs)
    Dim candidateItems As New List(Of DocInfo)
    For Each doc In selectedDocs
        candidateItems.Add(New DocInfo(doc.GetTitle(), doc.GetPathName()))
    Next

    docsInfo.AddUnique(candidateItems)
End Sub

( doc 和 selectedDocs 变量超出了这个问题的范围。)

现在,重要的一点 - GetTitle() 和 GetPathName() 在每次按钮单击时返回相同的字符串(我在单击之间选择了相同的文档)。这意味着添加到候选项目,然后添加到 docsInfo 的 DocInfo 对象是相同的。然而,扩展函数 AddUnique 失败,导致列表中出现重复。

困惑的是,我在这些重复的 DocsInfo 类对象上运行了 GetHashCode():

    For Each docInfo In docsInfo
        Console.WriteLine(docInfo.title)
        Console.WriteLine(docInfo.fullPath)
        Console.WriteLine(docInfo.GetHashCode())
    Next

这是输出:

Assem1^Test assembly.SLDASM
C:\Users\Justinas\AppData\Local\Temp\swx5396\VC~~\Test assembly\Assem1^Test assembly.SLDASM
7759225

Assem1^Test assembly.SLDASM
C:\Users\Justinas\AppData\Local\Temp\swx5396\VC~~\Test assembly\Assem1^Test assembly.SLDASM
14797678

每次单击按钮时,我都会得到相同的 DocsInfo 对象(title 和 fullPath 属性具有相同的值),但它们的哈希值每次都不同,而且我能想到的每一次比较,都无法承认这些对象是出于所有意图和目的相同的。

为什么会这样?以及如何修复 AddUnique 扩展功能以按预期工作?

标签: vb.netlistobjecthashcomparison

解决方案


这种行为是因为 .NET 中“引用”类型和“值”类型之间的差异。这些的基本理念是,对于“引用”类型,对象标识优先于内容(即,具有相同内容的两个不同对象实例仍然被认为是不同的),而对于“值”类型,内容是唯一的这很重要。

在 VB 中,Class表示引用类型,而Structure表示值类型。它们各自的行为是你所期望的,那么:默认情况下,Equalson aClass等价于ReferenceEquals,检查引用是否相同,并GetHashCode根据对象标识返回一个值。Equalson aStructure执行成员值相等,并GetHashCode根据成员的哈希码返回一个值。

有几个不同的选项可以覆盖默认行为,具有不同的影响和侵入程度。

您可以更改ClassStructure. 如果你这样做,我强烈建议消除它们上的任何可变行为(即,使所有字段和属性ReadOnly),因为 mutable Structures 可能非常难以正确推理。但是,如果您确实拥有不可变数据,那么这是最容易维护的,因为 .NET 已经可以满足您的需求,您不必维护自己的数据EqualsGetHashCode覆盖。

您可以覆盖GetHashCodeEquals在您Class的行为上像Structure版本一样。这不会改变你的类的任何其他内容,但它会使它像一个值类型一样用于容器和序列的目的。如果您担心维护,另一种选择是基于反射做一些事情,尽管这不应该用于任何高吞吐量的事情,因为反射通常不是特别高效。

我相信散列和排序容器采用可选的构造函数参数,它可以让你提供一个类来覆盖内容的行为而不改变它Class本身。你可以做这样的事情。我建议查看 MSDN 文档以获取HashSet.


推荐阅读