web-dev-qa-db-ja.com

コンテンツに基づいて、オブジェクトの一意のハッシュコードを生成する方法

オブジェクトのコンテンツに基づいて、オブジェクトの一意のハッシュコードを生成する必要があります。 DateTime(2011,06,04)は、DateTime(2011,06,04)と等しくなければなりません。

  • .GetHashCode()を使用することはできません。異なる内容のオブジェクトに対して同じハッシュコードを生成する可能性があるためです。
  • 同じ内容のオブジェクトに対して異なるハッシュコードを生成するため、ObjectIDGeneratorの.GetIDを使用できません。
  • オブジェクトに他のサブオブジェクトが含まれている場合は、これらを再帰的にチェックする必要があります。
  • コレクションに取り組む必要があります。

これを書く必要がある理由は? PostSharpを使用してキャッシングレイヤーを書いています。

更新

間違った質問をしていたのではないでしょうか。 Jon Skeetが指摘したように、安全のために、オブジェクト内の潜在的なデータの組み合わせと同じ数だけ、キャッシュキーに一意の組み合わせが必要です。したがって、最善の解決策は、リフレクションを使用して、オブジェクトのパブリックプロパティをエンコードする長い文字列を作成することです。オブジェクトは大きすぎないため、これは非常に迅速かつ効率的です。

  • キャッシュキーを作成するのが効率的です(オブジェクトのパブリックプロパティを大きな文字列に変換するだけです)。
  • キャッシュヒットをチェックするのが効率的です(2つの文字列を比較してください)。
17
Contango

niqueハッシュコードを作成する必要がある場合は、基本的に、型が持つことができるのと同じ数の状態を表すことができる数について話していることになります。 DateTimeよりも、ティックの値とDateTimeKindを取ることを意味すると、私は信じています。

Ticksプロパティの上位2ビットがゼロになると想定し、それらを使用して種類を格納することで、問題を回避できる場合があります。つまり、私が知る限り、7307年まで大丈夫です。

private static ulong Hash(DateTime when)
{
    ulong kind = (ulong) (int) when.Kind;
    return (kind << 62) | (ulong) when.Ticks;
}
16
Jon Skeet

コメントから:

オブジェクトの内容に基づいてGUID=のようなものがほしい

それは珍しい要件のようですが、それはあなたの要件なので、計算してみましょう。

10兆兆兆年の間、1年間に10億個のオブジェクト(毎秒30個)を作成するとします。それは10です49 あなたが作成しているユニークなオブジェクト。数学の計算は非常に簡単です。 その時点で少なくとも1回のハッシュ衝突の確率が10分の1を超える18 ハッシュのビットサイズが384未満の場合

したがって、必要なレベルの一意性を確保するには、少なくとも384ビットのハッシュコードが必要です。これは、12 int32sの便利なサイズです。 1秒間に30を超えるオブジェクトを作成する場合、または確率を10分の1未満にしたい場合18 その後、より多くのビットが必要になります。

なぜそんなに厳しい要件があるのですか?

ここに私があなたの要求された要件があるとしたらどうするでしょうか。最初の問題は、考えられるすべてのデータを自己記述的なビットのシーケンスに変換することです。すでにシリアル化フォーマットがある場合は、それを使用してください。そうでない場合は、ハッシュに関心のあるすべての可能なオブジェクトをシリアル化できるものを発明してください。

次に、オブジェクトをハッシュするには、オブジェクトをバイト配列にシリアル化してから、バイト配列をSHA-384またはSHA-512ハッシュアルゴリズムで実行します。これにより、攻撃者が衝突を強制しようとする場合でもユニークであると考えられている、プロの暗号グレードの384ビットまたは512ビットのハッシュが生成されます。その数ビットは、10兆兆兆年の時間枠での衝突の可能性を低く抑えるのに十分以上でなければなりません。

36
Eric Lippert

ここではハッシュコードについて話しているのではなく、状態の数値表現が必要です。これを一意にするには、オブジェクトの構造によっては非常に大きくする必要がある場合があります。

これを書く必要がある理由は? PostSharpを使用してキャッシングレイヤーを書いています。

代わりに通常のハッシュコードを使用して、オブジェクトを実際に比較して衝突を処理してみませんか?それが最も合理的なアプローチのようです。

12
BrokenGlass

BrokenGlassの回答への追加。これは私が投票して正解と見なしたものです。

GetHashCode/Equalsメソッドを使用するということは、2つのオブジェクトが同じ値にハッシュする場合、それらのEquals実装を使用して、それらが等しいかどうかを判断することになります。

これらのオブジェクトがEqualsをオーバーライドしない限り(これは、実際にはIEquatable<T>を実装することを意味しますが、Tはそのタイプです)、Equalsのデフォルト実装は、参照比較。これは、ビジネスの意味で「等しい」が独立して構築されたオブジェクトに対して、キャッシュが誤ってミスを生成することを意味します。

キャッシュの使用モデルを慎重に検討してくださいIEquatableではないクラスで、参照以外の等しいオブジェクトをチェックすることが予想される方法で使用することになるため)同等の場合、キャッシュは完全に役に立たないになります。

3
Jon

まったく同じ要件があり、ここに私が思いついた機能があります。これは、キャッシュする必要があるオブジェクトのタイプに適しています。

public static string CreateCacheKey(this object obj, string propName = null)
{
    var sb = new StringBuilder();
    if (obj.GetType().IsValueType || obj is string)
        sb.AppendFormat("{0}_{1}|", propName, obj);
    else
        foreach (var prop in obj.GetType().GetProperties())
        {
            if (typeof(IEnumerable<object>).IsAssignableFrom(prop.PropertyType))
            {
                var get = prop.GetGetMethod();
                if (!get.IsStatic && get.GetParameters().Length == 0)
                {
                    var collection = (IEnumerable<object>)get.Invoke(obj, null);
                    if (collection != null)
                        foreach (var o in collection)
                            sb.Append(o.CreateCacheKey(prop.Name));
                }
            }
            else
                sb.AppendFormat("{0}{1}_{2}|", propName, prop.Name, prop.GetValue(obj, null));

        }
    return sb.ToString();
}

たとえば、次のような場合

var bar = new Bar()
{
    PropString = "test string",
    PropInt = 9,
    PropBool = true,
    PropListString = new List<string>() {"list string 1", "list string 2"},
    PropListFoo =
        new List<Foo>()
            {new Foo() {PropString = "foo 1 string"}, new Foo() {PropString = "foo 2 string"}},
    PropListTuple =
        new List<Tuple<string, int>>()
            {
                new Tuple<string, int>("Tuple 1 string", 1), new Tuple<string, int>("Tuple 2 string", 2)
            }
};

var cacheKey = bar.CreateCacheKey();

上記の方法で生成されたキャッシュキーは

PropString_test文字列| PropInt_9 | PropBool_True | PropListString_list文字列1 | PropListString_list文字列2 | PropListFooPropString_foo 1文字列| PropListFooPropString_foo 2文字列| PropListTupleItem1_Tuple |文字列| PropListTupleItem2_1ItemPropListTuplele2

3
asmiki

Jsonにシリアル化されたオブジェクトからex md5合計(またはそのようなもの)を計算できます。一部のプロパティのみを問題にしたい場合は、途中で匿名オブジェクトを作成できます。

 public static string GetChecksum(this YourClass obj)
    {
        var copy = new
        {
           obj.Prop1,
           obj.Prop2
        };
        var json = JsonConvert.SerializeObject(ob);

        return json.CalculateMD5Hash();
    }

私は、ライセンスベースのデータを格納しているデータベースが誰かに悪用されていないかどうかを確認するために使用します。 json変数にシードを追加して複雑にすることもできます

3

.GetHashCode()を使用することはできません。異なる内容のオブジェクトに対して同じハッシュコードを生成する可能性があるためです。

ハッシュコードが衝突するのはごく普通のことです。ハッシュコードが固定長(標準の.NETハッシュコードの場合は32ビット)の場合、これより大きい範囲の値(たとえば、64ビットのロング; n * 64)との衝突が発生します。 n longの配列のビットなど)。

実際、有限長Nのハッシュコードの場合、Nを超える要素のコレクションでは常に衝突が発生します。

あなたが求めていることは、一般的なケースでは現実的ではありません。

3
Joe

この拡張方法はあなたの目的に合っていますか?オブジェクトが値型の場合は、ハッシュコードを返すだけです。そうでない場合は、各プロパティの値を再帰的に取得し、それらを単一のハッシュに結合します。

using System.Reflection;

public static class HashCode
{
    public static ulong CreateHashCode(this object obj)
    {
        ulong hash = 0;
        Type objType = obj.GetType();

        if (objType.IsValueType || obj is string)
        {
            unchecked
            {
                hash = (uint)obj.GetHashCode() * 397;
            }

            return hash;
        }

        unchecked
        {
            foreach (PropertyInfo property in obj.GetType().GetProperties())
            {
                object value = property.GetValue(obj, null);
                hash ^= value.CreateHashCode();
            }
        }

        return hash;
    }
}
1
fre0n