【发布时间】:2010-11-27 07:14:06
【问题描述】:
我已将 Dictionary(TKey, TValue) 用于多种用途。但是我没有遇到任何实现 GetHashCode() 的场景,我相信这是因为我的键是主要类型,如 int 和 string。 我很想知道当一个人应该使用自定义对象作为键并因此实现 GetHashCode() Equals() 等方法时的场景(现实世界的例子)。
而且,使用自定义对象作为键是否需要实现这些功能?
【问题讨论】:
我已将 Dictionary(TKey, TValue) 用于多种用途。但是我没有遇到任何实现 GetHashCode() 的场景,我相信这是因为我的键是主要类型,如 int 和 string。 我很想知道当一个人应该使用自定义对象作为键并因此实现 GetHashCode() Equals() 等方法时的场景(现实世界的例子)。
而且,使用自定义对象作为键是否需要实现这些功能?
【问题讨论】:
澄清一下:关于Dictionary<TKey, TValue> 和GetHashCode() 有一件重要的事情:字典使用GetHashCode 来确定两个键是否相等,即如果<TKey> 是自定义类型,您应该关心实现GetHashCode()小心。正如 Andrew Hare 指出的那样,这很容易,如果您有一个简单的类型可以明确地识别您的自定义对象。如果你有一个组合标识符,它会变得有点复杂。
例如,将复数视为TKey。复数由其实部和虚部决定。两者都是简单类型,例如double。但是,如果两个复数相等,您将如何识别呢?您为您的自定义复杂类型实现 GetHashCode() 并结合这两个识别部分。
您可以进一步阅读后者here。
更新
根据 Ergwun 的评论,我检查了 Dictionary<TKey, TValue>.Add 的行为,特别注意 TKey 对 Equals(object) 和 GetHashCode() 的实现。一世
必须承认,我对结果感到相当惊讶。
给定两个TKey类型的对象k1和k2,两个TValue类型的任意对象v1和v2,以及一个Dictionary<TKey, TValue>类型的空字典d,这是将v1 和k1 添加到d 第一个和v2 和k2 第二个时会发生什么(取决于TKey.Equals(object) 和TKey.GetHashCode() 的实现):
k1.Equals(k2) k1.GetHashCode() == k2.GetHashCode() d.Add(k2, v2)
false false ok
false true ok
true false ok
true true System.ArgumentException
结论:我错了,因为我最初认为第二种情况(Equals 返回false,但两个关键对象具有相同的哈希码)会引发ArgumentException。但正如第三种情况所示,字典在某种程度上确实使用了GetHashCode()。无论如何,两个相同类型且相等的对象必须返回相同的哈希码以确保实例 Dictionary<TKey, TValue> 正常工作似乎是个好建议。
【讨论】:
GetHashCode() 来确定两个键是否相等。也就是说,字典可以包含单独的条目,其键具有相同的哈希码。字典可能效率较低,但它仍然可以工作。
一个例子是当您需要创建一个复合键(即由多条数据组成的键)时。该复合键将是需要覆盖这些方法的自定义类型。
例如,假设您有一个地址记录的内存缓存,并且您想检查某个地址是否在缓存中,以节省前往数据库检索它的昂贵费用。我们还假设地址在其 street 1 和 zip code 字段方面是唯一的。你可以用这样的方式来实现你的缓存:
class AddressCacheKey
{
public String StreetOne { get; set; }
public String ZipCode { get; set; }
// overrides for Equals and GetHashCode
}
和
static Dictionary<AddressCacheKey,Address> cache;
由于您的 AddressCacheKey 类型覆盖了 Equals 和 GetHashCode 方法,它们将是字典中键的良好候选者,您将能够确定是否需要访问数据库根据多条数据检索记录。
【讨论】:
这里有两个问题。
让我们从 1 开始。如果您正在编写一个可能被其他人使用的类,那么当引用 Equals() 还不够时,您将需要定义 GetHashCode() 和 Equals()。如果您不打算在字典中使用它,并且它是供您自己使用的,那么我认为没有理由跳过 GetHashCode() 等。
对于 2),您应该在需要从一个对象到其他类型的恒定时间查找的任何时候使用一个对象。由于 GetHashCode() 返回一个数值,并且集合存储引用,因此在 Int 或字符串上使用 Object 不会受到任何惩罚(记住字符串是对象)。
【讨论】:
只要默认的Object.Equals(引用相等性测试)不够用,您就应该覆盖Equals 和GetHashCode。例如,当您的键的类型是自定义类型并且您希望两个键被视为相等时,即使它们不是自定义类型的同一个实例,也会发生这种情况。
例如,如果您的密钥很简单
class Point {
public int X { get; set; }
public int Y { get; set; }
}
如果两个Points 相等并且它们的Ys 相等,那么您希望两个Points 被认为相等,那么您将需要覆盖Equals 和GetHashCode。
【讨论】:
point.X 交换为 point.Y,则 Equals 和 GetHashCode 需要是什么?是否必须删除该值并重新添加到字典中?