简体   繁体   English

在 GetHashCode 中计算哈希码的类

[英]Class to calculate hash codes in GetHashCode

I use an XOR based implementation in the GetHashCode implementation of most of my equatable types.我在大多数 Equatable 类型的 GetHashCode 实现中使用基于 XOR 的实现。

I've read several posts explaining why it is not the best solution so I decided to implement GetHashCode as suggested by Jon Skeet :我已经阅读了几篇文章,解释了为什么它不是最佳解决方案,所以我决定按照 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;
}

Since the code is likely to be similar in most implementations, I tried to build a helper class to calculate hash codes for all my classes.由于代码在大多数实现中可能是相似的,我尝试构建一个辅助类来计算所有类的哈希码。 It should be an easy thing to do but one of the main constraints with GetHashCode is it has to be fast.这应该是一件容易的事情,但 GetHashCode 的主要限制之一是它必须很快。 Therefore any implementation involving allocation is probably a no go (for instance, the use of a non static class).因此,任何涉及分配的实现都可能是行不通的(例如,使用非静态类)。

Ideally a call to such a method would look like:理想情况下,对此类方法的调用如下所示:

public override GetHashCode() => HashCodeCalculator.Calculate(X, Y, Z);

And have all the logic (unchecked + primes + null check...).并拥有所有逻辑(未检查 + 素数 + 空检查...)。 But the use of a params parameter implicitly creates an array.但是使用params参数隐式地创建了一个数组。

Is it best to duplicate the hashing algorithm in each class instead?最好在每个类中复制哈希算法吗? Or is a class like the following as efficient?或者像下面这样的类是否有效?

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

which can then be used like this:然后可以像这样使用:

public override int GetHashCode()
{
  int result = HashCalculator.Add(Prop1);
  result = HashCalculator.Add(result, Prop2);

  return result;
}

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

I can see why it is so tempting to have some kind of helper tool to calc hashes, but in this case efficiency is in great contradiction to convenience.我可以理解为什么使用某种辅助工具来计算哈希值如此诱人,但在这种情况下,效率与便利性大相径庭。 You are trying to have a cookie and eat it and the answer depends on how much cookie you are willing to left over :)你想吃一块饼干,答案取决于你愿意留下多少饼干:)

  • One additional method call?一个额外的方法调用? Then it should have signature simmilar to int HashCode(params int subhashcodes) but invoking it will be ugly because you need to provide hashcodes of fields as parameters.然后它应该具有类似于int HashCode(params int subhashcodes)签名,但是调用它会很丑陋,因为您需要提供字段的哈希码作为参数。
  • One method call and boxing?一种方法调用和拳击? Then you can change int to object in previous signature to call fields hashcodes inside your method (I'm not fully sure that there will be no boxing in first case - feel free to correct me)然后,您可以在先前的签名中将int更改为object以在您的方法中调用字段哈希码(我不完全确定在第一种情况下不会有装箱 - 请随时纠正我)

Personally I will stick to writing it by hand (or by Resharper).我个人会坚持手写(或由 Resharper)。

After benchmarking it appears that using a struct like the following is almost as efficient as XORing and nicely encapsulates hash codes calculation.经过基准测试,使用如下结构几乎与 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;
}

which can be used like this:可以这样使用:

public override int GetHashCode() => new HashCode(Prop1) + Prop2;

EDIT: .net core now has such a HashCode struct .编辑:.net core 现在有这样一个HashCode struct

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM