20

我正在尝试获取对象的哈希(md5 或 sha)。

我已经实现了这个: http ://alexmg.com/post/2009/04/16/Compute-any-hash-for-any-object-in-C.aspx

我正在使用 nHibernate 从数据库中检索我的 POCO。
在此运行 GetHash 时,每次从数据库中选择和补充它时,它都是不同的。我想这是意料之中的,因为底层代理会改变。

反正,

有没有办法每次都一致地获取对象上所有属性的哈希值?

我玩弄过在 this.GetType().GetProperties 上使用 StringBuilder 并在其上创建哈希的想法,但这似乎效率低下?

作为旁注,这是为了将这些实体从一个数据库 (RDBMS) 更改跟踪到 NoSQL 存储(比较哈希值以查看对象是否在 rdbms 和 nosql 之间更改)

4

3 回答 3

24

如果你不覆盖GetHashCode你只是继承Object.GetHashCodeObject.GetHashCode基本上只是返回实例的内存地址,如果它是一个引用对象。当然,每次加载一个对象时,它很可能会被加载到内存的不同部分,从而产生不同的哈希码。

这是否是正确的做法值得商榷。但这就是“过去”实施的,所以现在不能改变。

如果您想要一致的东西,那么您必须GetHashCode根据对象的“值”(即属性和/或字段)覆盖并创建代码。这可以像分布式合并所有属性/字段的哈希码一样简单。或者,它可能像您需要的那样复杂。 如果您正在寻找的只是区分两个不同对象的东西,那么在对象上使用唯一键可能对您有用。如果您正在寻找更改跟踪,则使用哈希的唯一键可能不起作用

我只是使用字段的所有哈希码为父对象创建一个合理分布的哈希码。例如:

public override int GetHashCode()
{
    unchecked
    {
        int result = (Name != null ? Name.GetHashCode() : 0);
        result = (result*397) ^ (Street != null ? Street.GetHashCode() : 0);
        result = (result*397) ^ Age;
        return result;
    }
}

素数 397 的使用是为一个值生成一个唯一的数字,以更好地分布散列码。有关在哈希码计算中使用素数的更多详细信息,请参阅http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/

当然,您可以使用反射来获取所有属性来执行此操作,但这会更慢。或者,您可以使用CodeDOM动态生成代码,以根据属性的反射生成散列并缓存该代码(即生成一次并在下次重新加载它)。但是,这当然非常复杂,可能不值得付出努力。

MD5 或 SHA 哈希或 CRC 通常基于数据块。如果你想要这样,那么使用每个属性的哈希码是没有意义的。正如 Henk 所描述的,可能将数据序列化到内存并以这种方式计算哈希值会更适用。

于 2012-09-13T03:48:10.800 回答
11

如果此“哈希”仅用于确定实体是否已更改,则以下算法可能会有所帮助(注意,它未经测试并假设在生成哈希时将使用相同的运行时(否则对“简单”类型的 GetHashCode 的依赖是不正确的)):

public static byte[] Hash<T>(T entity) 
{
  var seen = new HashSet<object>();
  var properties = GetAllSimpleProperties(entity, seen);
  return properties.Select(p => BitConverter.GetBytes(p.GetHashCode()).AsEnumerable()).Aggregate((ag, next) => ag.Concat(next)).ToArray();
}

private static IEnumerable<object> GetAllSimpleProperties<T>(T entity, HashSet<object> seen)
{
  foreach (var property in PropertiesOf<T>.All(entity))
  {
    if (property is int || property is long || property is string ...) yield return property;
    else if (seen.Add(property)) // Handle cyclic references
    {
      foreach (var simple in GetAllSimpleProperties(property, seen)) yield return simple;
    }
  }
}

private static class PropertiesOf<T>
{
  private static readonly List<Func<T, dynamic>> Properties = new List<Func<T, dynamic>>();

  static PropertiesOf()
  {
    foreach (var property in typeof(T).GetProperties())
    {
      var getMethod = property.GetGetMethod();
      var function = (Func<T, dynamic>)Delegate.CreateDelegate(typeof(Func<T, dynamic>), getMethod);
      Properties.Add(function);
    }
  }

  public static IEnumerable<dynamic> All(T entity) 
  {
    return Properties.Select(p => p(entity)).Where(v => v != null);
  }
} 

这将可以像这样使用:

var entity1 = LoadEntityFromRdbms();
var entity2 = LoadEntityFromNoSql();
var hash1 = Hash(entity1);
var hash2 = Hash(entity2);
Assert.IsTrue(hash1.SequenceEqual(hash2));
于 2012-09-12T18:02:53.327 回答
-1

GetHashCode() 返回一个 Int32(不是 MD5)。

如果您使用基本或系统 GetHashCode() 创建两个具有所有相同属性值的对象,它们将不会具有相同的 Hash。

String 是一个对象,也是一个例外。

string s1 = "john";
string s2 = "john";
if (s1 == s2) returns true and will return the same GetHashCode()

如果要控制两个对象的相等比较,则应覆盖 GetHash 和 Equality。

如果两个对象相同,那么它们也必须具有相同的 GetHash()。但是具有相同 GetHash() 的两个对象不一定相同。比较将首先测试 GetHash(),如果它在那里匹配,它将测试 Equals。好的,有一些比较直接进入 Equals,但您仍然应该覆盖两者并确保两个相同的对象产生相同的 GetHash。

我用它来同步客户端和服务器。您可以使用所有属性,也可以让任何属性更改更改 VerID。这里的优点是更简单更快的 GetHashCode()。在我的情况下,我已经使用任何属性更改来重置 VerID。

    public override bool Equals(Object obj)
    {
        //Check for null and compare run-time types.
        if (obj == null || !(obj is FTSdocWord)) return false;
        FTSdocWord item = (FTSdocWord)obj;
        return (OjbID == item.ObjID && VerID == item.VerID);
    }
    public override int GetHashCode()
    {
        return ObjID ^ VerID;
    }

我最终只使用 ObjID,所以我可以执行以下操作

if (myClientObj == myServerObj && myClientObj.VerID <> myServerObj.VerID)
{
   // need to synch
}

Object.GetHashCode 方法

具有相同属性值的两个对象。他们平等吗?它们产生相同的 GetHashCode() 吗?

            personDefault pd1 = new personDefault("John");
            personDefault pd2 = new personDefault("John");
            System.Diagnostics.Debug.WriteLine(po1.GetHashCode().ToString());
            System.Diagnostics.Debug.WriteLine(po2.GetHashCode().ToString()); 
            // different GetHashCode
            if (pd1.Equals(pd2))  // returns false
            {
                System.Diagnostics.Debug.WriteLine("pd1 == pd2");
            }
            List<personDefault> personsDefault = new List<personDefault>();
            personsDefault.Add(pd1);
            if (personsDefault.Contains(pd2))  // returns false
            {
                System.Diagnostics.Debug.WriteLine("Contains(pd2)");
            }

            personOverRide po1 = new personOverRide("John");
            personOverRide po2 = new personOverRide("John");
            System.Diagnostics.Debug.WriteLine(po1.GetHashCode().ToString());
            System.Diagnostics.Debug.WriteLine(po2.GetHashCode().ToString());  
            // same hash
            if (po1.Equals(po2))  // returns true
            {
                System.Diagnostics.Debug.WriteLine("po1 == po2");
            }
            List<personOverRide> personsOverRide = new List<personOverRide>();
            personsOverRide.Add(po1);
            if (personsOverRide.Contains(po2))  // returns true
            {
                System.Diagnostics.Debug.WriteLine("Contains(p02)");
            }
        }



        public class personDefault
        {
            public string Name { get; private set; }
            public personDefault(string name) { Name = name; }
        }

        public class personOverRide: Object
        {
            public string Name { get; private set; }
            public personOverRide(string name) { Name = name; }

            public override bool Equals(Object obj)
            {
                //Check for null and compare run-time types.
                if (obj == null || !(obj is personOverRide)) return false;
                personOverRide item = (personOverRide)obj;
                return (Name == item.Name);
            }
            public override int GetHashCode()
            {
                return Name.GetHashCode();
            }
        }
于 2012-09-12T20:28:32.137 回答