簡體   English   中英

在 GetHashCode 中計算哈希碼的類

[英]Class to calculate hash codes in GetHashCode

我在大多數 Equatable 類型的 GetHashCode 實現中使用基於 XOR 的實現。

我已經閱讀了幾篇文章,解釋了為什么它不是最佳解決方案,所以我決定按照 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);

並擁有所有邏輯(未檢查 + 素數 + 空檢查...)。 但是使用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 struct

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM