2016-10-24 7 views
0

私は、同等のタイプのほとんどのGetHashCode実装でXORベースの実装を使用します。私はseveral postsそれは私がGetHashCodeメソッドas suggested by 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の主な制約の1つは、高速でなければならないことです。したがって、割り当てを含む実装はおそらく無意味です(たとえば、静的でないクラスの使用など)。

理想的には、このようなメソッドの呼び出しは次のようになります。

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

答えて

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

(あなたが停止することを決定するまで、2、3、4、など)あなたは、パラメータの様々な小さな固定番号のオーバーロードを作成することができ、中配列の割り当てを避けるためには、、特にのオペランドが多数ある場合にのみ、使用する必要があるオーバーヘッドをparamsにする必要があります。その時点で、配列割り当てのオーバーヘッドは問題になりにくい完了した作業のうち、より小さなパーセンテージになります)。

+0

私はこれについて考えたが、私は> 10個の特性が関与しているいくつかの例があります。ディクショナリのGetHashCode呼び出しごとに配列が作成されると、パフォーマンスに影響が出ます。 –

+0

@ vc74実際に10個のプロパティがある十分な用途がある場合、ベンチマークを行ってアレイの割り当てが問題であると判断した場合は、最大10個(またはそれ以上)のオペランドになるオーバーロードを作成します。 – Servy

+0

それについて考えると、それは実行可能な解決策だと思います。私はオーバーロードを重複させる考えは本当に好きではありませんが、これは他の方法がないコーナーケースになる可能性があります。 params IEnumerableは将来のソリューション(いつか実装されている場合)ですが、paramsと同じパフォーマンスの問題がある可能性があります。 –

0

私は、なぜcalcのハッシュにヘルパーツールを使用するのが魅力的かを知ることができますが、この場合効率は利便性と大きな矛盾しています。あなたはクッキーを持っていて、それを食べようとしています。答えはどれくらいのクッキーを残しておきますか?

  • もう1つのメソッド呼び出し?それで、それは int HashCode(params int subhashcodes) と同様の署名を持つべきですが、それを呼び出すことは、フィールドのハッシュコードをパラメータとして提供する必要があるため、醜いでしょう。
  • 1つのメソッドコールとボクシング?私が固執する
個人的に

- そして、あなたは(私を修正すること自由に感じ、私は最初のケースにはボクシングがないことを完全にはよく分からない)あなたのメソッド内のフィールドのハッシュコードを呼び出すために、以前の署名にobjectintを変更することができますそれを手で書くこと(またはResharperによって)。

+0

それはパフォーマンスに大きな影響を与えますが、それはパラメータで私が心配している醜さです –

+0

もちろん、あなたは1つから特定のNまでのパラメータのオーバーロードを生成することができます。しかし、これについてもっと考えると、早すぎる最適化はすべての悪の根源です。したがって、最も便利なメソッドを使用し、このオブジェクトが頻繁に使用されることがわかっている場合にのみ手でコードを作成します。 1つのケースが欠けている場合は、プロファイラで簡単に見つけることができます。 – Bart

関連する問題