使用可变对象作为字典中的键完全可以吗?

发布于 2024-09-06 20:35:52 字数 2536 浏览 7 评论 0原文

假设我有一些特殊的类 WrappedDataTable,并且我想将每个 WrappedDataTable 与一个 DataTable 关联起来。此外,我希望对于任何给定的 DataTable 来说,存在的 WrappedDataTable 不超过一个。

一位同事建议我可以缓存我的 WrappedDataTable 并使用工厂方法来访问它,如下所示:

public static class DataTableWrapper
{
    private Dictionary<DataTable, WrappedDataTable> _wrappedTables;

    static DataTableWrapper()
    {
        _wrappedTables = new Dictionary<DataTable, WrappedDataTable>();
    }

    public static WrappedDataTable Wrap(this DataTable table)
    {
        WrappedDataTable wrappedTable;
        if (!_wrappedTables.TryGetValue(table, out wrappedTable))
            _wrappedTables[table] = wrappedTable = new WrappedDataTable(table);

        return wrappedTable;
    }
}

一开始这让我觉得非常可疑,我想是因为我已经熟悉了键入的想法字典应该是不可变类型。但或许情况并不一定如此?一项快速测试告诉我,DataTable 似乎在对其内容进行多次修改的过程中保持一致的哈希码;因此,Dictionary 似乎能够一致地为 ContainsKey 返回正确的值。

我想知道的是,默认情况下 object.GetHashCode 的基本版本是否会为每个单独的对象返回不变的值,或者我在 DataTable 中看到的是只是一个幻觉?

如果前者为真 - 并且 object.GetHashCode 工作得很好 - 似乎“仅使用不可变类型作为键”建议实际上仅适用于以下场景:

  1. 您希望对象相等值相等而不是引用相等,和/或:
  2. 您有一个自定义类型,它有自己的基于类型成员的 GetHashCode 实现。

有哪位圣人愿意为我阐明这一点吗?


更新:感谢 Jon Skeet 回答我的问题。在其他新闻中,我做了一些挖掘,并认为我想出了一个IEqualityComparer,它确实提供了身份比较!检查一下(抱歉,讨厌 VB.NET 的人,我刚刚创建了一个 VB.NET 项目,所以这就是我写的内容——翻译很简单):

Imports System.Collections.Generic
Imports System.Runtime.CompilerServices

Public Class IdentityComparer(Of T As Class)
    Implements IEqualityComparer(Of T)

    Public Overloads Function Equals(ByVal x As T, ByVal y As T) As Boolean _
        Implements IEqualityComparer(Of T).Equals

        Return Object.ReferenceEquals(x, y)
    End Function

    Public Overloads Function GetHashCode(ByVal obj As T) As Integer _
        Implements IEqualityComparer(Of T).GetHashCode

        Return RuntimeHelpers.GetHashCode(obj)
    End Function
End Class

看一下这个示例程序:

Dim comparer As IEqualityComparer(Of String) = New IdentityComparer(Of String)

Dim x As New String("Hello there")
Dim y As New String("Hello there")

Console.WriteLine(comparer.Equals(x, y))
Console.WriteLine(comparer.GetHashCode(x))
Console.WriteLine(comparer.GetHashCode(y))

输出:

False
37121646
45592480

Say I have some special class, WrappedDataTable, and I want to associate each WrappedDataTable with exactly one DataTable. Furthermore, I want there to be no more than one WrappedDataTable in existence for any given DataTable.

A colleague suggested I could cache my WrappedDataTable and use a factory method to access one, like this:

public static class DataTableWrapper
{
    private Dictionary<DataTable, WrappedDataTable> _wrappedTables;

    static DataTableWrapper()
    {
        _wrappedTables = new Dictionary<DataTable, WrappedDataTable>();
    }

    public static WrappedDataTable Wrap(this DataTable table)
    {
        WrappedDataTable wrappedTable;
        if (!_wrappedTables.TryGetValue(table, out wrappedTable))
            _wrappedTables[table] = wrappedTable = new WrappedDataTable(table);

        return wrappedTable;
    }
}

This struck me as very questionable at first, I guess because I've become familiar with the idea that keys in a dictionary should be immutable types. But perhaps this is not necessarily the case? A quick test revealed to me that a DataTable appears to maintain a consistent hash code over the course of numerous modifications to its contents; a Dictionary<DataTable, TValue> therefore appears to be able to return a correct value for ContainsKey consistently.

What I'm wondering is if the base version of object.GetHashCode by default will return an unchanging value for every individual object, or if what I'm seeing with DataTable is just an illusion?

If the former is true -- and object.GetHashCode works just fine -- it seems the "use only immutable types as keys" advice really only applies to scenarios where:

  1. You want equality of objects to be about value equality as opposed to reference equality, and/or:
  2. You have a custom type with its own GetHashCode implementation that is based on the type's members.

Any sages out there care to shed some light on this for me?


UPDATE: Thanks to Jon Skeet for answering my question. In other news, I did some digging and think I came up with an IEqualityComparer<T> that does provide identity comparison after all! Check it out (sorry VB.NET haters, I just had a VB.NET project up so that's what I wrote it in -- translation is trivial):

Imports System.Collections.Generic
Imports System.Runtime.CompilerServices

Public Class IdentityComparer(Of T As Class)
    Implements IEqualityComparer(Of T)

    Public Overloads Function Equals(ByVal x As T, ByVal y As T) As Boolean _
        Implements IEqualityComparer(Of T).Equals

        Return Object.ReferenceEquals(x, y)
    End Function

    Public Overloads Function GetHashCode(ByVal obj As T) As Integer _
        Implements IEqualityComparer(Of T).GetHashCode

        Return RuntimeHelpers.GetHashCode(obj)
    End Function
End Class

Take a look at this example program:

Dim comparer As IEqualityComparer(Of String) = New IdentityComparer(Of String)

Dim x As New String("Hello there")
Dim y As New String("Hello there")

Console.WriteLine(comparer.Equals(x, y))
Console.WriteLine(comparer.GetHashCode(x))
Console.WriteLine(comparer.GetHashCode(y))

Output:

False
37121646
45592480

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

表情可笑 2024-09-13 20:35:52

它不必返回唯一的值。它只需要返回一个不变的值 - 这就是 object.GetHashCode 所做的。

只要 DataTable 不覆盖 EqualsGetHashCode,您基本上就获得了相等的对象标识 - 这意味着它并不重要如果对象发生了突变。

我个人希望看到 IEqualityComparer 的实现,它为任何类型提供身份相等性,但我们无法自己实现 - 没有找出如果没有被覆盖的话GetHashCode返回什么的方法。 (Java 在其标准库中具有此功能,但 .NET 没有。Grr。)

编辑:Woot - 使用 object.ReferenceEqualsRuntimeHelpers.GetHashCode(),我们可以轻松实现 IdentityEqualityComparer。耶!

It doesn't have to return a unique value. It just has to return an unchanging one - and that's what object.GetHashCode does.

So long as DataTable doesn't override Equals or GetHashCode, you've basically got object identity as equality - which means it doesn't matter if the object is mutated.

Personally I'd like to see an implementation of IEqualityComparer<T> which provides identity equality for any type, but we can't implement that ourselves - there's no way of finding out what GetHashCode would have returned if it hadn't been overridden. (Java has this capability in its standard libraries, but .NET doesn't. Grr.)

EDIT: Woot - with object.ReferenceEquals and RuntimeHelpers.GetHashCode(), we can easily implement an IdentityEqualityComparer<T>. Yay!

云柯 2024-09-13 20:35:52

我认为你对事情的理解很好。对于要存储在字典中的对象,任何会影响其哈希值或与其他对象相等的测试的特征都必须是不可变的。不会影响这些事物的特征不必是一成不变的。

I think you understand things fine. For an object to be stored in a dictionary, any characteristics which would affect its hash value or tests for equality with other objects must be immutable. Characteristics which would not affect those things need not be immutable.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文