vb.net - 列表中相同的对象会产生不同的哈希值并且无法通过比较测试
问题描述
我有一个奇怪的问题。我想使用一个函数来实现对 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 扩展功能以按预期工作?
解决方案
这种行为是因为 .NET 中“引用”类型和“值”类型之间的差异。这些的基本理念是,对于“引用”类型,对象标识优先于内容(即,具有相同内容的两个不同对象实例仍然被认为是不同的),而对于“值”类型,内容是唯一的这很重要。
在 VB 中,Class
表示引用类型,而Structure
表示值类型。它们各自的行为是你所期望的,那么:默认情况下,Equals
on aClass
等价于ReferenceEquals
,检查引用是否相同,并GetHashCode
根据对象标识返回一个值。Equals
on aStructure
执行成员值相等,并GetHashCode
根据成员的哈希码返回一个值。
有几个不同的选项可以覆盖默认行为,具有不同的影响和侵入程度。
您可以更改Class
为Structure
. 如果你这样做,我强烈建议消除它们上的任何可变行为(即,使所有字段和属性ReadOnly
),因为 mutable Structure
s 可能非常难以正确推理。但是,如果您确实拥有不可变数据,那么这是最容易维护的,因为 .NET 已经可以满足您的需求,您不必维护自己的数据Equals
或GetHashCode
覆盖。
您可以覆盖GetHashCode
并Equals
在您Class
的行为上像Structure
版本一样。这不会改变你的类的任何其他内容,但它会使它像一个值类型一样用于容器和序列的目的。如果您担心维护,另一种选择是基于反射做一些事情,尽管这不应该用于任何高吞吐量的事情,因为反射通常不是特别高效。
我相信散列和排序容器采用可选的构造函数参数,它可以让你提供一个类来覆盖内容的行为而不改变它Class
本身。你可以做这样的事情。我建议查看 MSDN 文档以获取HashSet
.
推荐阅读
- reactjs - React styled-components 不将样式应用于组件
- html - 如何使用 CSS 选择父元素?
- reactjs - 使用 jest 和酶模拟反应组件的自定义服务
- angular - 如何在 Angular 中使用 firebase 实现 reCaptcha
- css - 如何通过 django 模板在浏览器中以多行显示 textarea 内容?
- c - 有什么方法可以显示 WinDBG 中可执行文件调用的所有函数(不仅仅是调用堆栈)?
- c++ - (一致)为什么 std:: 在第一个线程之前使它打印第二个?
- javascript - Discord.JS 反应
- python - 为什么按下键时此图像不移动?
- javascript - 无法在 ComponentDidMount 中使用 redux 数据