我有一个奇怪的问题。我想实现一个扩展列表与一个函数合并另一个列表到它不包括重复的值:
<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()在每次单击按钮时返回相同的字符串(我在单击之间选择了相同的文档)。这意味着添加到candidateItems和添加到docsInfo的DocInfo对象是相同的。但是,扩展函数AddUnique失败,导致列表中出现重复项。
困惑,我运行GetHashCode()对这些重复的DocsInfo类对象:
For Each docInfo In docsInfo
Console.WriteLine(docInfo.title)
Console.WriteLine(docInfo.fullPath)
Console.WriteLine(docInfo.GetHashCode())
Next
输出:
Assem1^Test assembly.SLDASM
C:UsersJustinasAppDataLocalTempswx5396VC~~Test assemblyAssem1^Test assembly.SLDASM
7759225
Assem1^Test assembly.SLDASM
C:UsersJustinasAppDataLocalTempswx5396VC~~Test assemblyAssem1^Test assembly.SLDASM
14797678
每次单击按钮,我都得到相同的DocsInfo对象(title和fullPath属性具有相同的值),但它们的哈希值每次都不同,并且我能想到的每个比较都不能承认这些对象在所有意图和目的上都是相同的。
为什么会发生这种情况?我如何修复AddUnique扩展函数,使其按预期工作?
这种行为是由于。net中的" reference " &;类型和"值";类型。它们的基本哲学是为了"参考"。类型,对象标识优先于内容(即,具有相同内容的两个不同对象实例仍然被认为是不同的),而对于"Value"类型,内容是唯一重要的。
在VB中,Class
表示引用类型,Structure
表示值类型。它们各自的行为是您所期望的,那么:默认情况下,Equals
在Class
上相当于ReferenceEquals
,检查引用是否相同,GetHashCode
根据对象标识返回一个值。Structure
上的Equals
按成员值相等,GetHashCode
根据成员的哈希码返回一个值。
有几个不同的选项可以覆盖默认行为,具有不同的影响和侵入性级别。
您可以将Class
更改为Structure
。如果你这样做,我强烈建议消除它们上的任何可变行为(即使所有字段和属性都成为ReadOnly
),因为可变的Structure
很难正确推理。如果你真的有不可变数据,这是最容易维护的,因为。net已经做了你想做的,你不需要维护你自己的Equals
或GetHashCode
覆盖。
您可以在Class
上覆盖GetHashCode
和Equals
,以像Structure
版本一样运行。这不会改变您的类的任何其他内容,但它会使它像容器和序列的值类型一样。如果您担心维护问题,另一种选择是做一些基于反射的事情,尽管这不应该用于任何需要高吞吐量的事情,因为反射通常不是特别高性能。
我相信哈希和排序容器采用可选的构造函数参数,这将允许您提供一个类来覆盖内容的行为,而不改变Class
本身。你可以这样做。我建议查看HashSet
的MSDN文档。