类来计算GetHashCode中的哈希码
本文关键字:哈希码 GetHashCode 计算 | 更新日期: 2023-09-27 18:14:51
我在大多数等价类型的GetHashCode实现中使用基于异或的实现。
我读了几篇文章解释为什么它不是最好的解决方案,所以我决定实现由Jon Skeet建议的GetHashCode:
unchecked // Overflow is fine, just wrap
{
int hash = 17;
hash = hash * 23 + field1.GetHashCode();
hash = hash * 23 + field2.GetHashCode();
hash = hash * 23 + field3.GetHashCode();
return hash;
}
由于代码在大多数实现中可能是相似的,因此我尝试构建一个辅助类来计算所有类的哈希码。这应该是一件容易的事情,但GetHashCode的主要限制之一是它必须快。因此,任何涉及分配的实现都可能是不允许的(例如,使用非静态类)。
理想情况下,对这种方法的调用应该是这样的:public override GetHashCode() => HashCodeCalculator.Calculate(X, Y, Z);
并具有所有逻辑(未检查+素数+ null检查…)。但是使用params
形参会隐式地创建一个数组。
是否最好在每个类中复制哈希算法?还是像下面这样的类同样有效?
public static class HashCalculator
{
private const int _seed = 5923;
private const int _multiplier = 7481;
public static int Add(object value) => Add(_seed, value);
public static int Add(int current, object value)
{
int valueHashCode = (value != null) ? value.GetHashCode() : 0;
unchecked
{
return (current * _multiplier) + valueHashCode;
}
}
}
可以这样使用:
public override int GetHashCode()
{
int result = HashCalculator.Add(Prop1);
result = HashCalculator.Add(result, Prop2);
return result;
}
您可以创建过载为各种小型固定数量的参数(2、3、4等,直到你决定停止),为了避免数组分配,然后有一个params
过载时,只需要使用有一个特别大量的操作数,此时数组的开销分配不太可能是一个问题(这将是一个小比例的工作)。
我可以理解为什么使用某种辅助工具来计算哈希值是如此诱人,但是在这种情况下,效率与便利性是严重矛盾的。你想要一块饼干并吃掉它,答案取决于你愿意剩下多少饼干:)
- 一个额外的方法调用?那么它应该有类似的签名
int HashCode(params int subhashcodes)
但是调用它会很难看,因为你需要提供字段的哈希码作为参数。 - 一个方法调用和装箱?然后你可以在之前的签名中将
int
更改为object
,以在你的方法中调用字段哈希码(我不完全确定在第一种情况下不会有装箱-请随时纠正我)
我个人会坚持手写(或Resharper)。
经过基准测试后,似乎使用下面这样的结构体几乎与XORing一样有效,并且很好地封装了哈希码计算。
/// <summary>
/// Calculates a hash code based on multiple hash codes.
/// </summary>
public struct HashCode
{
private const int _seed = 5923;
private const int _multiplier = 7481;
/// <summary>
/// Builds a new hash code.
/// </summary>
/// <returns>The built hash code.</returns>
public static HashCode Build() => new HashCode(_seed);
/// <summary>
/// Constructor from a hash value.
/// </summary>
/// <param name="value">Hash value.</param>
private HashCode(int value)
{
_value = value;
}
/// <summary>
/// Builds a new hash code and initializes it from a hash code source.
/// </summary>
/// <param name="hashCodeSource">Item from which a hash code can be extracted (using GetHashCode).</param>
public HashCode(object hashCodeSource)
{
int sourceHashCode = GetHashCode(hashCodeSource);
_value = AddValue(_seed, sourceHashCode);
}
private readonly int _value;
/// <summary>
/// Returns the hash code for a given hash code source (0 if the source is null).
/// </summary>
/// <param name="hashCodeSource">Item from which a hash code can be extracted (using GetHashCode).</param>
/// <returns>The hash code.</returns>
private static int GetHashCode(object hashCodeSource) => (hashCodeSource != null) ? hashCodeSource.GetHashCode() : 0;
/// <summary>
/// Adds a new hash value to a hash code.
/// </summary>
/// <param name="currentValue">Current hash value.</param>
/// <param name="valueToAdd">Value to add.</param>
/// <returns>The new hash value.</returns>
private static int AddValue(int currentValue, int valueToAdd)
{
unchecked
{
return (currentValue * _multiplier) + valueToAdd;
}
}
/// <summary>
/// Adds an object's hash code.
/// </summary>
/// <param name="hashCode">Hash code to which the object's hash code has to be added.</param>
/// <param name="hashCodeSource">Item from which a hash code can be extracted (using GetHashCode).</param>
/// <returns>The updated hash instance.</returns>
public static HashCode operator +(HashCode hashCode, object hashCodeSource)
{
int sourceHashCode = GetHashCode(hashCodeSource);
int newHashValue = AddValue(hashCode._value, sourceHashCode);
return new HashCode(newHashValue);
}
/// <summary>
/// Implicit cast operator to int.
/// </summary>
/// <param name="hashCode">Hash code to convert.</param>
public static implicit operator int(HashCode hashCode) => hashCode._value;
}
可以这样使用:
public override int GetHashCode() => new HashCode(Prop1) + Prop2;
编辑:.net core现在有了这样一个HashCode结构