用于计算GetHashCode

时间:2016-10-24 14:41:00

标签: c# dictionary hash

我在大多数equatable类型的GetHashCode实现中使用基于XOR的实现。

我已阅读several posts解释为什么它不是最佳解决方案所以我决定实施GetHashCode as suggested by Jon Skeet

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);

拥有所有逻辑(未选中+ primes + null check ...)。但是使用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;
}

3 个答案:

答案 0 :(得分:3)

您可以为各种小的固定数量的参数(2,3,4等等)创建重载,直到您决定停止为止,以避免数组分配,然后只有params重载当存在特别大量操作数时需要使用,此时数组分配的开销不太可能成为问题(因为它将是工作的较小百分比)完成)。

答案 1 :(得分:0)

我可以理解为什么使用某种辅助工具来计算哈希是如此诱人,但在这种情况下,效率与方便性相矛盾。你正试图吃一个饼干并吃它,答案取决于你愿意遗留多少饼干:)

  • 另外一种方法电话?然后它应该有签名simmilar int HashCode(params int subhashcodes) 但是调用它会很难看,因为你需要提供字段的哈希码作为参数。
  • 一种方法调用和拳击?然后你可以在之前的签名中将int改为object来调用你方法中的字段哈希码(我不完全确定在第一种情况下不会有拳击 - 随意纠正我)

我个人会坚持用手写(或者用Resharper)。

答案 2 :(得分:0)

在基准测试之后,似乎使用像下面这样的结构几乎与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;