2012-09-12 22 views
7

Estoy intentando conseguir un hash (MD5 o SHA) de un objeto.Generar hash del objeto constantemente

He implementado esto: http://alexmg.com/post/2009/04/16/Compute-any-hash-for-any-object-in-C.aspx

estoy usando NHibernate para recuperar mis POCOs a partir de una base de datos.
Al ejecutar GetHash en esto, es diferente cada vez que se selecciona e hidrata de la base de datos. Supongo que esto se espera, ya que los proxies subyacentes cambiarán.

De todos modos,

¿Hay una manera de obtener un hash de todas las propiedades de un objeto, consistente cada vez?

He jugado con la idea de usar un StringBuilder sobre esto.GetType(). GetProperties ..... y crear un hash sobre eso, ¿pero parece ineficaz?

Como nota al margen, esto es para el cambio de seguimiento de estas entidades de una base de datos (RDBMS) a una tienda NoSQL (comparando los valores hash para ver si los objetos cambiados entre RDBMS y nosql)

+0

¿Los hash se almacenan entre sesiones? –

+1

Más información sobre cómo serializar deserializar estos objetos. ¿Y está sobrescribiendo GetHashCode()? – Paparazzi

Respuesta

13

Si usted no está anulando GetHashCode que acaba de heredar Object.GetHashCode. Object.GetHashCode básicamente devuelve la dirección de memoria de la instancia, si se trata de un objeto de referencia. Por supuesto, cada vez que se carga un objeto, es probable que se cargue en una parte diferente de la memoria y, por lo tanto, se obtenga un código hash diferente.

Es discutible si eso es lo correcto; pero eso es lo que se implementó "de vuelta en el día", por lo que no puede cambiar ahora.

Si desea algo consistente, debe sobrescribir GetHashCode y crear un código basado en el "valor" del objeto (es decir, las propiedades y/o campos). Esto puede ser tan simple como una fusión distribuida de los códigos hash de todas las propiedades/campos. O bien, podría ser tan complicado como lo necesites. Si todo lo que está buscando es algo para diferenciar dos objetos diferentes, entonces puede ser útil usar una clave única en el objeto. Si está buscando el seguimiento de cambios, usar la clave única para el hash probablemente no va a funcionar

Simplemente uso todos los códigos hash de los campos para crear un código hash razonablemente distribuido para el objeto primario. Por ejemplo:

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

El uso del número primo 397 es generar un número único para un valor de distribuir mejor el código hash. Consulte http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/ para obtener más detalles sobre el uso de números primos en los cálculos del código hash.

Podría, por supuesto, usar el reflejo para obtener todas las propiedades para hacer esto, pero sería más lento. Alternativamente, puede usar el CodeDOM para generar código dinámicamente para generar el hash basado en la reflexión sobre las propiedades y la caché de ese código (es decir, generarlo una vez y volverlo a cargar la próxima vez). Pero, por supuesto, esto es muy complejo y puede no valer la pena el esfuerzo.

Un hash MD5 o SHA o CRC generalmente se basa en un bloque de datos. Si quieres eso, entonces usar el código hash de cada propiedad no tiene sentido. Posiblemente serializar los datos en la memoria y calcular el hash de esa manera sería más aplicable, como lo describe Henk.

+0

'Object.GetHashCode' no devuelve la dirección de memoria de la instancia, ya que esto puede cambiar durante GC. En realidad, es solo un número aleatorio generado en el primer acceso y apuntado por el encabezado del objeto. Para obtener más información, lea SyncBlockIndex, que se utiliza para Hash Code and Monitor, entre otras cosas. – andrewjs

6

Si este hash 'se usa únicamente para determinar si las entidades han cambiado entonces el siguiente algoritmo puede ayudar (NB que no se ha probado y se supone que se utilizará el mismo tiempo de ejecución cuando la generación de hashes (de lo contrario la dependencia de GetHashCode de 'tipos simples' es incorrecta)):

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

Esto sería entonces utilizable así:

var entity1 = LoadEntityFromRdbms(); 
var entity2 = LoadEntityFromNoSql(); 
var hash1 = Hash(entity1); 
var hash2 = Hash(entity2); 
Assert.IsTrue(hash1.SequenceEqual(hash2)); 
-1

GetHashCode() devuelve un Int32 (no un MD5).

Si crea dos objetos con los mismos valores de propiedad que no tendrán el mismo hash si se utiliza el sistema de base o GetHashCode().

cadena es un objeto y una excepción.

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

Si desea controlar comparación de igualdad de dos objetos entonces debería anular el GetHash e Igualdad.

Si dos objetos son iguales, entonces también deben tener el mismo GetHash(). Pero dos objetos con el mismo GetHash() no son necesariamente lo mismo. Una comparación probará primero el GetHash() y si obtiene una coincidencia allí probará los equivalentes. De acuerdo, hay algunas comparaciones que van directamente a Equals, pero aún así debes anular ambas y asegurarte de que dos objetos idénticos produzcan el mismo GetHash.

lo uso para la sincronización de un cliente con el servidor. Podría usar todas las Propiedades o podría tener cualquier cambio de Propiedad cambiar el VerID. La ventaja aquí es un GetHashCode() más simple y rápido. En mi caso, estaba restableciendo el VerID con cualquier cambio de propiedad.

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

Terminé usando ObjID solo, así que podía hacer las siguientes

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

Object.GetHashCode Method

Dos objetos con los mismos valores de la propiedad. ¿Son iguales? ¿Producen el mismo 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(); 
      } 
     } 
+0

@exacerbatedexpert Pero ese es exactamente el punto. Cualquier cambio no es necesariamente una nueva versión. Serialize/deserialzie podría introducir un cambio en MD5 de un objeto que no ha sido cambiado realmente. Si uso una camisa diferente mañana, ¿soy una persona diferente? Más único no es el punto. GetHashCode por sí solo no determina la singularidad. Igual determina la singularidad. El propósito de GetHashCode es una forma económica de reducir el número de llamadas a un Igual más caro. Es la base de HashSet y Dictionary – Paparazzi

+0

@exacerbatedexpert Pero, ¿qué pasa con el caso de deserialize que usa el sistema GetHash y el objeto con las mismas propiedades no tiene el mismo MD5 debido a un GetHash aleatorio? – Paparazzi

+0

@exacerbatedexpert Consulte la pregunta "Al ejecutar GetHash en esto, es diferente cada vez que se selecciona e hidrata de la base de datos". La serialización correcta puede no usar GetHashCode directamente, pero el Object sí lo hace. – Paparazzi

Cuestiones relacionadas