【问题标题】:Class to calculate hash codes in GetHashCode在 GetHashCode 中计算哈希码的类
【发布时间】:2017-03-06 09:30:50
【问题描述】:

我在大多数可等式类型的 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);

并拥有所有逻辑(未检查 + 素数 + 空检查...)。但是使用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;
}

【问题讨论】:

    标签: c# dictionary hash


    【解决方案1】:

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

    【讨论】:

    • 我想过这个,但我有几个案例涉及 > 10 个属性。如果在 Dictionary 中为每个 GetHashCode 调用创建一个数组,则会影响性能。
    • @vc74 如果您有足够的使用情况,实际上有 10 个属性,则创建最多 10 个(或更多)操作数的重载,如果您已完成基准测试以确定数组分配是问题。
    • 想一想,我想这是一个可行的解决方案。我真的不喜欢复制重载的想法,但这可能是一个极端情况,没有其他方法可以做到这一点。 params IEnumerable 可能是未来的解决方案(如果有一天实施),但很可能它们与 params 有相同的性能问题。
    • @vc74 即使添加了它,您仍然需要创建可枚举对象,这很可能无法实现比数组更好。
    【解决方案2】:

    我明白为什么使用某种辅助工具来计算哈希值如此诱人,但在这种情况下,效率与便利性大相径庭。你想吃一块饼干,答案取决于你愿意留下多少饼干:)

    • 一个额外的方法调用?然后它应该有类似的签名 int HashCode(params int subhashcodes) 但是调用它会很丑,因为您需要提供字段的哈希码作为参数。
    • 一个方法调用和装箱?然后您可以在之前的签名中将 int 更改为 object 以在您的方法中调用字段哈希码(我不完全确定在第一种情况下不会有装箱 - 请随时纠正我)

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

    【讨论】:

    • 这更多的是对性能的影响,而不是让我担心参数的丑陋
    • 当然,您可以为从一个到任何特定 N 的参数生成重载。但是我对此思考得越多,我就越认为正如 Donald Knuth 曾经说过的那样“过早的优化是万恶之源” '。因此,请使用最方便的方法,仅当您知道该对象将被大量使用时才手动编写代码。如果你错过了一个案例,使用分析器很容易找到它。
    【解决方案3】:

    经过基准测试,使用如下结构似乎几乎与 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 struct

    【讨论】:

      猜你喜欢
      • 2017-02-04
      • 2016-06-23
      • 2022-10-02
      • 1970-01-01
      • 2011-03-08
      • 2012-03-16
      • 2012-07-12
      • 2011-11-16
      相关资源
      最近更新 更多