不可变类型的哈希码

时间:2009-06-01 16:47:45

标签: c# .net immutability

是否有关于哈希码的不可变类型的任何考虑因素?

我应该在构造函数中生成一次吗?

您如何明确哈希码是否已修复?我是不是该?如果是这样,使用名为HashCode的属性而不是GetHashCode方法更好吗?会有任何缺点吗? (考虑两者都有效,但建议使用该物业。)

7 个答案:

答案 0 :(得分:18)

  

是否有关于哈希码的不可变类型的任何考虑因素?

不可变类型是最容易正确散列的类型;散列可变数据时会发生大多数哈希码错误。最重要的是哈希和平等一致;如果两个实例比较相等,则它们应具有相同的哈希码。 (反过来不一定是真的;具有相同散列的两个实例不必相等。)

  

我应该在构造函数中生成一次吗?

这是一种性能优化技术;通过这样做,您可以交换增加的空间消耗(用于存储计算值)以减少可能的时间。我永远不会进行性能优化,除非它们是由真实的,以客户为中心的性能测试驱动的,这些测试会根据记录的目标仔细衡量这两个选项的性能。如果您精心设计的实验表明(1)未能这样做会导致您错过目标,并且(2)这样做会导致您达到目标,那么您应该这样做。

  

您如何明确哈希码是否已修复?

我不明白这个问题。更改哈希码是例外,而不是规则。哈希码始终应该是不变的。如果对象的哈希码发生变化,那么对象可能会在哈希表中“丢失”,因此每个人都应该假设哈希码保持稳定。

  

最好使用名为HashCode的属性,而不是GetHashCode方法吗?

你的对象的哪个消费者会说“好吧,我可以调用GetHashCode(),这是一种保证在所有对象上的方法,但是我会调用这个HashCode getter做同样的事情”?你有这样的消费者吗?

如果您没有任何功能消费者,请不要提供该功能。

答案 1 :(得分:9)

我通常不会在构造函数中生成它,但在决定是否缓存它之前,我还想了解更多有关预期用法的信息。

您是否期待少量实例,这些实例会被大量扫描并且需要很长时间来计算哈希值?如果是这样,缓存可能是适当的。如果您期待大量潜在的“丢失”实例,我不会打扰缓存。

有趣的是,.NET和Java在这方面为String做出了不同的选择 - Java缓存哈希,而.NET则不然。鉴于许多字符串实例从不散列,而 散列的那些实例经常只被散列一次(例如插入哈希表)我认为我赞成.NET的决定

基本上你是在与速度交换记忆+复杂性。正如Michael所说,在使代码更复杂之前进行测试。当然,在某些情况下(例如对于类库),您无法准确预测实际使用情况,但在许多情况下,您会有一个非常好的主意。

你当然不需要一个单独的财产。除非有人更改对象的状态,否则哈希代码应始终保持不变 - 如果您的类型是不可变的,那么您已经禁止这样做,因此用户不应期待任何更改。只需覆盖GetHashCode()

答案 2 :(得分:4)

我会在第一次调用getHashCode时生成一次哈希码,然后将其缓存以供以后调用。这可以避免在可能不需要时在构造函数中调用它。

如果您不希望为每个值对象多次调用getHashCode,则可能根本不需要缓存该值。

答案 3 :(得分:3)

好吧,你必须有一个GetHashCode()重写方法,因为消费者将如何检索你的哈希码。大多数哈希码都是相当简单的算术运算,可以快速执行。您是否有理由相信缓存结果(具有内存成本)会给您带来明显的性能提升?

从简单开始 - 动态生成哈希码。如果你认为你会看到缓存它的性能改进,请先测试。

法规要求我在此时向您推荐“过早优化是所有邪恶的根源”。

答案 4 :(得分:2)

根据我个人的经验,开发人员非常擅长误判性能问题

所以它建议尽可能简单地保持一切,同时在GetHashCode()中动态计算哈希码。

答案 5 :(得分:1)

为什么需要确保哈希码是固定的?哈希码的语义是对于对象的任何给定状态它将始终是相同的值。由于您的对象是不可变的,因此这是给定的。您如何选择实施GetHashCode取决于您。

让它成为返回的私有字段是一种选择 - 它小巧,简单,快速。

答案 6 :(得分:1)

通常,计算HashCode应该很快。所以缓存不应该是一个优化,不值得麻烦。

如果分析确实显示GethashCode需要花费大量时间,那么可能应该将其缓存,作为修复。

但我不认为这是正常做法的一部分。