如何根据对象的内容为对象生成唯一的哈希码?

时间:2011-04-06 16:06:09

标签: c# .net visual-studio-2010 .net-4.0 hash

我需要根据对象的内容为对象生成唯一的哈希码,例如: DateTime(2011,06,04)应该等于DateTime(2011,06,04)。

  • 我不能使用.GetHashCode(),因为它可能会为具有不同内容的对象生成相同的哈希码。
  • 我无法使用ObjectIDGenerator中的.GetID,因为它会为具有相同内容的对象生成不同的哈希码。
  • 如果对象包含其他子对象,则需要以递归方式检查这些对象。
  • 它需要处理集合。

我需要写这个的原因?我正在使用PostSharp写一个缓存层。

更新

我想我可能一直在问错误的问题。正如Jon Skeet指出的那样,为了安全起见,我需要在缓存键中使用尽可能多的唯一组合,因为对象中存在潜在数据的组合。因此,最好的解决方案可能是使用反射构建一个长字符串,该字符串对对象的公共属性进行编码。对象不是太大,所以这非常快速有效:

  • 它有效地构造缓存键(只需将对象的公共属性转换为大字符串)。
  • 有效检查缓存命中(比较两个字符串)。

8 个答案:

答案 0 :(得分:35)

来自评论:

  

我喜欢基于对象内容的GUID之类的东西。我不介意每10万亿亿亿年左右偶尔会出现重复

这似乎是一个不寻常的要求,但由于这是你的要求,让我们做数学。

让我们假设您每年制作十亿个独特的物品 - 每秒30个 - 达10万亿亿亿亿年。这是您正在创建的10个 49 唯一对象。计算数学很容易; 当哈希的位大小小于384时,该时间内至少有一次哈希冲突的概率高于10 18 中的一个。

因此,您至少需要一个384位哈希码才能获得所需的唯一性级别。这是一个方便的大小,12个int32s。如果你每秒要制作超过30个物体,或者希望概率小于10 18 中的一个,那么就需要更多的位。

为什么你有这么严格的要求?

如果我满足您的要求,我会怎么做。第一个问题是将每个可能的数据转换为自描述的比特序列。如果您已经有序列化格式,请使用它。如果没有,请创建一个可以序列化您感兴趣的所有可能散列对象的对象。

然后,要散列对象,将其序列化为字节数组,然后通过SHA-384或SHA-512散列算法运行字节数组。这将产生一个专业的加密等级384或512位哈希,即使面对试图强迫碰撞的攻击者,它也被认为是独一无二的。在你的10万亿亿亿亿年的时间框架内,这么多位应足以确保低碰撞概率。

答案 1 :(得分:16)

如果您需要创建唯一哈希码,那么您基本上是在谈论一个可以代表您的类型可以拥有的状态的数字。对于DateTime而言,意味着采用Ticks值和DateTimeKind,我相信。

您可以假设Ticks属性的前两位将为零,并使用它们来存储该类型。这意味着,就我所知,你在7307年之前就可以了:

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

答案 2 :(得分:11)

你不是在谈论哈希代码,你需要一个代表你的状态的数字 - 因为它是唯一的,它可能必须非常大,这取决于你的对象结构。

  

我需要写这个的原因?我   使用编写缓存层   PostSharp。

为什么不使用常规哈希码,并通过实际比较对象来处理冲突?这似乎是最合理的方法。

答案 3 :(得分:3)

  

我不能使用.GetHashCode(),因为它可能会为具有不同内容的对象生成相同的哈希码。

哈希码发生冲突是很正常的。如果您的哈希码具有固定长度(在标准.NET哈希码的情况下为32位),则您必然会与范围大于此的任何值发生冲突(例如,64位长; n * 64 n个数组的位等等。)

事实上,对于任何长度为N的哈希码,对于超过N个元素的集合总是会发生冲突。

在一般情况下,你所要求的是不可行的。

答案 4 :(得分:3)

除了BrokenGlass的回答之外,我已经投票并认为是正确的:

使用GetHashCode / Equals方法意味着如果两个对象哈希到相同的值,您将依赖于Equals实现来告诉您它们是否相同。

除非这些对象覆盖Equals(这实际上意味着他们实现IEquatable<T> T是其类型),Equals的默认实现将会执行参考比较。这反过来意味着您的缓存会错误地为商业意义上“相等”但已独立构建的对象产生错误

仔细考虑您的缓存的使用模式,因为如果您最终将其用于非IEquatable的类,并且您希望检查非引用的方式 - 对于相等的平等对象,缓存将变成完全无用

答案 5 :(得分:3)

我们有完全相同的要求,这是我想出的功能。这适用于我们需要缓存的对象类型

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 string | PropInt_9 | PropBool_True | PropListString_list string 1 | PropListString_list string 2 | PropListFooPropString_foo 1 string | PropListFooPropString_foo 2 string | PropListTupleItem1_tuple 1 string | PropListTupleItem2_1 | PropListTupleItem1_tuple 2 string | PropListTupleItem2_2 |

答案 6 :(得分:3)

您可以从序列化为json的对象计算ex md5 sum(或类似的东西)。 如果只想要一些属性,可以在路上创建匿名对象:

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

        return json.CalculateMD5Hash();
    }

我用它来检查是否有人弄乱我的数据库存储基于许可证的数据。你也可以用一些种子附加json变量来复杂化东西

答案 7 :(得分:1)

此扩展方法是否适合您的目的?如果对象是值类型,则只返回其哈希码。否则,它以递归方式获取每个属性的值,并将它们组合成一个哈希值。

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