2009-07-02 10 views
5

Básicamente, tengo los siguientes hasta el momento:¿Cómo debo implementar Object.GetHashCode() para la igualdad compleja?

class Foo { 
    public override bool Equals(object obj) 
    { 
     Foo d = obj as Foo ; 
     if (d == null) 
      return false; 

     return this.Equals(d); 
    } 

    #region IEquatable<Foo> Members 

    public bool Equals(Foo other) 
    { 
     if (this.Guid != String.Empty && this.Guid == other.Guid) 
      return true; 
     else if (this.Guid != String.Empty || other.Guid != String.Empty) 
      return false; 

     if (this.Title == other.Title && 
      this.PublishDate == other.PublishDate && 
      this.Description == other.Description) 
      return true; 

     return false; 
    } 
} 

Por lo tanto, el problema es el siguiente: Tengo un campo no requiere Guid, que es un identificador único. Si esto no está establecido, entonces debo tratar de determinar la igualdad basada en métricas menos precisas como un intento de determinar si dos objetos son iguales. Esto funciona bien, pero hace GetHashCode() desordenado ... ¿Cómo debo hacerlo? Una implementación ingenua sería algo así como:

public override int GetHashCode() { 
    if (this.Guid != String.Empty) 
     return this.Guid.GetHashCode(); 

    int hash = 37; 
    hash = hash * 23 + this.Title.GetHashCode(); 
    hash = hash * 23 + this.PublishDate.GetHashCode(); 
    hash = hash * 23 + this.Description.GetHashCode(); 
    return hash; 
} 

Pero ¿cuáles son las posibilidades de que los dos tipos de hash de la colisión? Ciertamente, no esperaría que fuera 1 in 2 ** 32. ¿Es esta una mala idea, y si es así, cómo debería hacerlo?

+0

Es más importante que su algoritmo hash esté de acuerdo con su algoritmo de igualdad que la distribución sea uniforme. Recuerde, el propósito del hash es únicamente obtener una distribución decente en una tabla hash; siempre y cuando no estés masivamente inclinado a un cubo en particular, las probabilidades son buenas, estarás bien. Si está preocupado, elija un escenario razonable que el consumidor de su objeto pueda encontrar (por ejemplo, poniendo algunos cientos de ellos en un diccionario, si es razonable) y realice algunas pruebas de desempeño para ver si obtiene una calificación aceptable. resultados. –

+0

Lo que más he visto en el uso real era ~ 200, pero el uso típico es <30, así que probablemente tengas razón. –

+1

Diablos, con menos de 30 elementos, una búsqueda lineal en una lista vinculada es probablemente un rendimiento razonable. Puede devolver un código hash de cero siempre, tener 100% de posibilidades de colisión y obtener un rendimiento aceptable. El objetivo de tener una buena distribución de los códigos hash es hacer que el rendimiento sea escalable cuando el tamaño del diccionario aumenta. Puede tener una mala distribución y obtener buenos resultados si solo va a poner una pequeña cantidad de elementos en la tabla. –

Respuesta

4

No creo que haya un problema con el enfoque que ha elegido usar. Preocuparse demasiado por las colisiones hash casi siempre es una indicación de pensar demasiado el problema; siempre que el hash sea muy probable que sea diferente, deberías estar bien.

En última instancia, es posible que desee considerar omitir el Description de su hash de todos modos si es razonable esperar que la mayoría de las veces los objetos se puedan distinguir en función de su título y fecha de publicación (¿libros?).

Incluso podría considerar ignorar por completo el GUID en su función de hash, y solo usarlo en la implementación Equals para eliminar la ambigüedad del improbable (?) Caso de choques de hash.

+0

Aunque, obviamente, el GUID, si está presente, es probable que sea mucho más rápido que una cadena de título arbitraria ... por lo que podría ser una optimización de rendimiento factible. – jerryjvl

+0

Descripción debe incluirse en igualdad (y, por lo tanto, en el código hash) –

+0

Ah, y para el registro, artículos RSS. –

7

Un muy fácil hash code method for custom classes es a nivel de bit XOR cada uno de los códigos de campo códigos juntos. Puede ser tan simple como esto:

int hash = 0; 
hash ^= this.Title.GetHashCode(); 
hash ^= this.PublishDate.GetHashCode(); 
hash ^= this.Description.GetHashCode(); 
return hash; 

Desde el link above:

XOR tiene las siguientes propiedades agradables:

  • que no depende de la orden de cómputo.
  • No "desperdicia" bits. Si cambia incluso un bit en uno de los componentes, el valor final cambiará.
  • Es rápido, un solo ciclo incluso en la computadora más primitiva.
  • Conserva la distribución uniforme. Si las dos piezas que combinas están uniformemente distribuidas, así será la combinación. En otras palabras, no tiende a colapsar el rango del resumen en una banda más estrecha.

XOR no funciona bien si usted espera tener valores duplicados en sus campos como valores duplicados se anulan entre sí cuando XORed. Ya que has hashing juntos tres campos no relacionados que no deberían ser un problema en este caso.

+7

XOR que no depende del orden de cálculo es una espada de dos filos ... si tiene objetos con múltiples campos del mismo tipo (por ejemplo, dos fechas), cuando se intercambien estos objetos se verán igual 'al hash. – jerryjvl

Cuestiones relacionadas