C#字典中可以用什么作为键?

11

我来自Python的世界,在那里只有可哈希对象才可以用作字典的键。在C#中是否存在类似的限制?你能使用自定义类型作为字典的键吗?


7
理论上你可以使用任何类型作为键值。但在实践中,应该选择不可变类型作为键值,否则当键值发生变化时会带来很多麻烦。 - Patryk Ćwiek
1
@PatrykĆwiek 注意此评论 -- “只有 GetHashCode 值和由 Equals 定义的等效类需要是不可变的。所有类类型都定义了一个合适的等效类——引用相等——通常只有可变对象应该使用这个等效类。如果字典的目的是将该类型的实例与其他东西关联起来,那么拥有可变类类型作为字典键就没有任何问题。” - ChrisW
3个回答

18

字典键的要求是可以比较和可哈希。在.NET中,每种类型(除了指针类型)都派生自System.Object并且由于其Equals()方法始终可比较,所以它总是可比较的。而其GetHashCode()方法也使其可哈希。所以任何.NET类型都可以自动用作键。

如果你想使用自己的类型作为键,则只需要在需要重新定义对象标识时执行一些特殊操作。换句话说,如果你需要让两个不同的对象相等,则需要重写Equals()方法,通常比较对象的字段。然后还必须重写GetHashCode(),使相等的对象生成相同的哈希码。

如果该类型无法更改或您想为Dictionary自定义行为,则可以将自定义的IEqualityComparer<>传递给构造函数。请记住,使用自己的GetHashCode()生成的哈希码的质量决定了字典的效率。


也许比“可比较的”更好的术语是“公平的”(equitable)。请参考此链接:https://learn.microsoft.com/en-us/dotnet/api/system.iequatable-1。 - Theodor Zoulias

3
是的,关键点在于它们实现了(或具有良好的默认实现)GetHashCodeEqualsDictionary<T, K> 实现可以利用通用的 IEqualityComparer<T>
所有自定义类型都将带有GetHashCodeEquals的默认实现,因为它们是object的成员,但这个默认值可能并不总是与您的类型相关。
字典首先尝试获取哈希码以确定值将落入的桶。如果存在哈希冲突,则会回退到相等性(我认为是这样)。
请注意,您使用的键的类型(classstruct、基本类型等)可能会产生不同的性能特征。在我们的代码库中,我们发现struct中的默认GetHashCode实现不如我们自己重写的快。我们还发现,嵌套字典在访问时间方面表现更好,而单个具有复合键的字典则较差。

如果您使用显而易见的相等定义(即,如果所有字段都相等,则对象相等),那么默认的Equals和GetHashCode足够好了吧?(除了性能原因,但我还没有到那个地步) - static_rtti
3
对于一个 class 类型,其默认的相等性实现方式并非基于字段相等,而是基于引用相等。对于一个 struct 类型,我想它是基于值相等,前 x 个字节块或类似的某些奥秘进行比较的。个人认为,确保自定义类型作为键的实现方式被明确定义是很重要的,但同时我们很少使用自定义类型作为键。 - Adam Houldsworth
1
@static_rtti 不一定,所有引用类型默认使用引用相等性。 - Patryk Ćwiek
2
关于性能,你是正确的。值类型的GetHashCode默认实现使用反射来获取类型字段的值(http://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx)。这非常慢。另一方面,这为您提供了相当不错的默认行为。 - Charles Barthélemy
@CharlesBarthélemy 是的,默認情況下實際上就像您所期望的那樣,這是幸運的。我從來沒有意識到它使用了反射。 - Adam Houldsworth

2

可以,只需要实现 IEqualityComparer 接口,重写 GetHashCode 和 Equals 方法。


那么你必须自己完成所有工作吗?有什么可以帮助你实现哈希函数的东西吗? - static_rtti
2
“GetHasCode”是一个有趣的方法名称。默认实现为“return false;” :) - Tim Schmelter
@static_rtti:System.Object有一个默认的GetHashCode()实现,它可能只是基于对象的地址(或者在垃圾回收的情况下可能是一种ID)。其他一些对象,例如System.String,则覆盖它以返回与它们的内容相关的内容。对于所有其他情况,您可以使用IEqualityComparer的实现来自定义哈希处理。例如,查看StringComparer类:其每个成员都具有不同的IEqualityComparer实现,以匹配字符串比较,因此您可以实现区分大小写的哈希处理。 - Medinoc
@Suhan,你实际上不需要实现那个接口,但这样做可以说是一件好事。 - Adam Houldsworth
@Suhan 我知道,但是你的回答似乎暗示着这是“必需的”。 - Adam Houldsworth
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接