类来计算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;
}

类来计算GetHashCode中的哈希码

您可以创建过载为各种小型固定数量的参数(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结构