12

オーバーライドに関する StackOverflow のすべての質問と回答を読んだ後GetHashCode()、簡単で便利なオーバーライドのために次の拡張メソッドを作成しましたGetHashCode()

public static class ObjectExtensions
{
    private const int _seedPrimeNumber = 691;
    private const int _fieldPrimeNumber = 397;
    public static int GetHashCodeFromFields(this object obj, params object[] fields) {
        unchecked { //unchecked to prevent throwing overflow exception
            int hashCode = _seedPrimeNumber;
            for (int i = 0; i < fields.Length; i++)
                if (fields[i] != null)
                    hashCode *= _fieldPrimeNumber + fields[i].GetHashCode();
            return hashCode;
        }
    }
}

(私は基本的に誰かがそこに投稿したコードをリファクタリングしただけです。なぜなら、それが一般的に使用できることが本当に好きだからです)

私はこのように使用します:

    public override int GetHashCode() {
        return this.GetHashCodeFromFields(field1, field2, field3);
    }

このコードに問題はありますか?

4

9 に答える 9

3

少し前に、あなたの問題を解決できるかもしれないことをいくつか書きました...(実際、あなたが持っているシードを含めることでおそらく改善される可能性があります...)

とにかく、このプロジェクトは Essence ( http://essence.codeplex.com/ ) と呼ばれ、System.Linq.Expression ライブラリを使用して (属性に基づいて) Equals/GetHashCode/CompareTo/ToString の標準表現を生成します。引数リストに基づいて IEqualityComparer および IComparer クラスを作成できるためです。(他にもいくつかのアイデアがありますが、先に進む前にコミュニティからのフィードバックを得たいと考えています。)

(これが意味することは、手書きとほぼ同じくらい速いということです-そうでない主なものはCompareTo()です;Linq.Expressionsには3.5リリースの変数の概念がないため、一致しない場合に、基礎となるオブジェクトで CompareTo() を 2 回呼び出す. Linq.Expressions への DLR 拡張機能を使用すると、これが解決されます. エミット il を使用することもできたと思いますが、そのときはそれほど刺激を受けませんでした. .)

これは非常に単純なアイデアですが、これまでに見たことがありません。

要するに、私はそれを磨くことへの興味を失いました (これには、codeproject の記事を書いたり、コードの一部を文書化したりすることが含まれていたはずです)。興味を持っている。

(codeplex サイトにはダウンロード可能なパッケージがありません。ソースに移動してそれを入手してください。おお、それは f# で書かれています (ただし、すべてのテスト コードは c# で書かれています)。

とにかく、ここにプロジェクトのテストからのc#の例があります:

    // --------------------------------------------------------------------
    // USING THE ESSENCE LIBRARY:
    // --------------------------------------------------------------------
    [EssenceClass(UseIn = EssenceFunctions.All)]
    public class TestEssence : IEquatable<TestEssence>, IComparable<TestEssence>
    {
        [Essence(Order=0] public int MyInt           { get; set; }
        [Essence(Order=1] public string MyString     { get; set; }
        [Essence(Order=2] public DateTime MyDateTime { get; set; }

        public override int GetHashCode()                                { return Essence<TestEssence>.GetHashCodeStatic(this); }
    ...
    }

    // --------------------------------------------------------------------
    // EQUIVALENT HAND WRITTEN CODE:
    // --------------------------------------------------------------------
    public class TestManual
    {
        public int MyInt;
        public string MyString;
        public DateTime MyDateTime;

        public override int GetHashCode()
        {
            var x = MyInt.GetHashCode();
            x *= Essence<TestEssence>.HashCodeMultiplier;
            x ^= (MyString == null) ? 0 : MyString.GetHashCode();
            x *= Essence<TestEssence>.HashCodeMultiplier;
            x ^= MyDateTime.GetHashCode();
            return x;
        }
    ...
    }

とにかく、誰かが価値があると思うなら、このプロジェクトには磨きをかける必要がありますが、アイデアはそこにあります...

于 2009-07-11T09:48:50.770 に答える
3

それはそれを行うためのしっかりした方法のように見えます。

私の唯一の提案は、パフォーマンスを本当に心配している場合は、いくつかの一般的なケース (つまり、おそらく 1 ~ 4 個の引数) に対して汎用バージョンを追加することをお勧めします。そうすれば、これらのオブジェクト (小さなキー スタイルの複合オブジェクトである可能性が最も高い) の場合、メソッド、ループ、汎用値のボックス化などに渡す配列を作成するオーバーヘッドがなくなります。呼び出しの構文はまったく同じですが、その場合に少し最適化されたコードを実行します。もちろん、メンテナンスのトレードオフの価値があるかどうかを判断する前に、これについていくつかのパフォーマンス テストを実行します。

このようなもの:

public static int GetHashCodeFromFields<T1,T2,T3,T4>(this object obj, T1 obj1, T2 obj2, T3 obj3, T4 obj4) {
    int hashCode = _seedPrimeNumber;
    if(obj1 != null)
        hashCode *= _fieldPrimeNumber + obj1.GetHashCode();
    if(obj2 != null)
        hashCode *= _fieldPrimeNumber + obj2.GetHashCode();
    if(obj3 != null)
        hashCode *= _fieldPrimeNumber + obj3.GetHashCode();
    if(obj4 != null)
        hashCode *= _fieldPrimeNumber + obj4.GetHashCode();
    return hashCode;
}
于 2009-04-18T19:03:38.487 に答える
1

問題は 1 つだけです。値を渡すために an を使用する必要があるのは残念です。これはobject[]、関数に送信する値の型をボックス化するためです。ただし、他の人が提案したような一般的なオーバーロードを作成するルートに行かない限り、選択肢はあまりないと思います。

于 2009-04-18T16:37:06.733 に答える
0

uncheckedここではあまり重要ではありませんが、一般的な原則として、合理的にできる限り範囲を狭くする必要があります。それ以外は、問題ないようです。

于 2009-04-18T16:40:58.410 に答える
0
public override int GetHashCode() {
    return this.GetHashCodeFromFields(field1, field2, field3, this);
}

(はい、私は非常に衒学的ですが、これは私が見る唯一の問題です)

于 2009-04-18T16:47:15.070 に答える
0

の使用に起因する問題とは別にparams object[] fields、型情報を使用しないことは、状況によってはパフォーマンスの問題になる可能性があると思います。2 つのクラスAB同じ型とフィールド数を持ち、同じインターフェースを実装しているとしますI。ここで、フィールドが同じでタイプが異なるオブジェクトにオブジェクトを配置するAと、同じバケットになります。おそらく次のようなステートメントを挿入しますBDictionary<I, anything>hashCode ^= GetType().GetHashCode();

Jonathan Rupp の受け入れられた回答は params 配列を扱いますが、値の型のボックス化は扱いません。したがって、パフォーマンスが非常に重要な場合は、おそらくGetHashCodeFromFieldsオブジェクトではなくintパラメーターを持つことを宣言し、フィールド自体ではなくフィールドのハッシュ コードを送信します。すなわち

public override int GetHashCode() 
{
    return this.GetHashCodeFromFields(field1.GetHashCode(), field2.GetHashCode());
}
于 2011-12-25T10:49:38.163 に答える
0

より最適:

  1. リフレクションを使用してビジネス オブジェクト フィールドを調べ、GetHashCode() (および Equals()) をオーバーライドする新しい部分クラスを作成するコード ジェネレーターを作成します。
  2. プログラムがデバッグ モードで起動するときにコード ジェネレーターを実行し、コードが変更されている場合は、開発者に再コンパイルするようメッセージを表示して終了します。

これの利点は次のとおりです。

  • リフレクションを使用すると、どのフィールドが値型であるかどうかがわかるため、null チェックが必要かどうかがわかります。
  • 余分な関数呼び出しやリストの作成などのオーバーヘッドはありません。これは、多くの辞書検索を行う場合に重要です。
  • 長い実装 (多数のフィールドを持つクラス) は、重要なビジネス コードから離れた部分クラスに隠されます。

短所:

  • GetHashCode() への多くの辞書検索/呼び出しを行わない場合は、やり過ぎです。
于 2009-04-19T01:33:40.860 に答える
0

発生する可能性のある問題の 1 つは、次のコードで多くのプロパティを持つオブジェクトで経験したように、乗算が 0 になると最終的な hashCode が常に 0 になることです。

hashCode *= _fieldPrimeNumber + fields[i].GetHashCode();

私はお勧めします:

hashCode = hashCode * _fieldPrimeNumber + fields[i].GetHashCode();

または、次のような xor に似たもの

hashCode = hashCode * _fieldPrimeNumber ^ fields[i].GetHashCode();
于 2015-01-08T00:16:32.607 に答える
0

GetHashCode を実装している間は、ほとんど割り当てを行うべきではないことを指摘しておく必要があります (これに関する役立つブログ投稿がいくつか あります)。

動作する方法params(オンザフライで新しい配列を生成する) は、これが実際には良い一般的な解決策ではないことを意味します。フィールドごとにメソッド呼び出しを使用し、それらに渡される変数としてハッシュ状態を維持することをお勧めします (これにより、より優れたハッシュ関数と雪崩も簡単に使用できます)。

于 2009-04-19T09:22:34.943 に答える