我正在尝试获取对象的哈希值(md5或sha)。
我实现了这个: http://alexmg.com/post/2009/04/16/Compute-any-hash-for-any-object-in-C.aspx
我正在使用nHibernate从数据库中检索我的POCO 在此运行GetHash时,每次从数据库中选择并保湿时都会有所不同。我想这是预期的,因为底层代理会改变。
反正
有没有办法在对象上获取所有属性的哈希值,每次都一致?
我已经想到了使用StringBuilder而不是this.GetType()。GetProperties .....并在其上创建一个哈希,但这似乎效率低下?
作为旁注,这是为了将这些实体从一个数据库(RDBMS)跟踪到NoSQL存储 (比较哈希值以查看对象是否在rdbms和nosql之间发生了变化)
答案 0 :(得分:17)
如果您没有覆盖GetHashCode
,则只需继承Object.GetHashCode
。 Object.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所描述的那样,可能将数据序列化到内存并以这种方式计算哈希将更加适用。
答案 1 :(得分:7)
如果此'hash'仅用于确定实体是否已更改,则以下算法可能会有所帮助(注意它未经测试,并假设在生成哈希时将使用相同的运行时(否则依赖于GetHashCode的'simple'类型不正确)):
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));
答案 2 :(得分:-1)
GetHashCode()返回一个Int32(不是MD5)。
如果使用所有相同的属性值创建两个对象,则在使用基本或系统GetHashCode()时它们将不具有相同的哈希值。
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
}
具有相同属性值的两个对象。他们是平等的吗?他们生成相同的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();
}
}