2012-05-12 8 views
15

en cuenta los siguientes objetos:aplicar GetHashCode() para objetos que contienen colecciones

class Route 
{ 
    public int Origin {get; set;} 
    public int Destination {get; set;} 
} 

Ruta implementa operadores de igualdad.

class Routing 
{ 
    public List<Route> Paths {get; set;} 
} 

que utiliza el código siguiente para implementar el método GetHashCode para el objeto de enrutamiento y parece que funciona, pero me pregunto si esa es la forma correcta de hacerlo? Confío en los controles de igualdad y, como no estoy seguro, pensé que les preguntaría a ustedes. ¿Puedo resumir los códigos hash o necesito hacer más magia para garantizar el efecto deseado?

public override int GetHashCode() 
     { 
      return (Paths != null 
         ? (Paths 
          .Select(p => p.GetHashCode()) 
          .Sum()) 
         : 0); 
     } 

yo nos registramos varias preguntas GetHashCode() aquí, así como el artículo de MSDN y de Eric Lippert sobre este tema, pero no pudo encontrar lo que estoy buscando.

+0

De esta manera es posible que tenga dos colecciones diferentes con mismo código hash. –

+1

¿Por qué no utilizar el 'GetHashCode' de la Colección en sí? – SimpleVar

+2

@ValBakhtin Solo hay 2 ** 32 códigos hash diferentes, por lo que no todas las colecciones pueden tener las suyas propias. –

Respuesta

12

Creo que su solución está bien. (Comentario mucho más tarde: el método Sum de LINQ actuará en el contexto checked, por lo que puede obtener fácilmente un OverflowException lo que significa que no es tan fino, después de todo). Pero es más habitual hacer XOR (adición sin acarreo). Por lo que podría ser algo así como

public override int GetHashCode() 
{ 
    int hc = 0; 
    if (Paths != null) 
    foreach (var p in Paths) 
     hc ^= p.GetHashCode(); 
    return hc; 
} 

Adición (después de la respuesta fue aceptada):

Recuerde que si alguna vez se utiliza este tipo Routing en un Dictionary<Routing, Whatever>, una otra situación HashSet<Routing> o en una tabla hash es utilizado, entonces su instancia será perdida si alguien altera (muta) el Routing después de que se haya agregado a la colección.

Si está seguro de que eso nunca sucederá, use mi código anterior. Dictionary<,> y demás seguirán funcionando si se asegura de que nadie altera el Routing al que se hace referencia.

Otra opción es simplemente escribir

public override int GetHashCode() 
{ 
    return 0; 
} 

si cree que nunca será utilizado el código hash. Si cada instancia devuelve 0 para código hash, obtendrá un rendimiento muy malo con tablas hash, pero su objeto no se perderá. Una tercera opción es arrojar un NotSupportedException.

+0

No estoy seguro de cómo devolver un Código Hash cambia fácilmente dentro del vínculo vital del objeto tiene sentido. Rompe el concepto de códigos hash. –

+0

@AndrewFinnell HashCode se puede usar para representar de manera única el estado interno de un objeto, y no solo para identificar la instancia única a lo largo de su vida útil. – SimpleVar

+0

@YoryeNathan Por supuesto, PUEDE ser, pero eso no significa que deba ser. Si coloca una instancia de este objeto en una colección hash, luego agrega una ruta de acceso, no lo encontrará. El código hash habrá cambiado. Esto derrota el propósito de GetHashCode(). –

0

No creo que sea una manera correcta de hacerlo, porque para determinar el hashcode final tiene que ser exclusivo para el objeto especificado. En su caso, usted hace un Sum(), que puede producir el mismo resultado con diferentes códigos hash en la recopilación (al final, los códigos hash son solo enteros).

Si su intención es determinar la igualdad en función del contenido de la colección, en este punto simplemente compare estas divisiones entre dos objetos. Es podría ser una operación que consume mucho tiempo, por cierto.

+0

Como dije en otro lugar , está bien que diferentes objetos compartan el mismo código hash. Después de todo, solo hay 2 ** 32 valores posibles de un 'int'. Pero un buen código hash debería golpear cada uno de los valores posibles con la misma frecuencia, en cierto modo. –

+0

No digo que no deberían tener el mismo código hash (si no, nunca serán eual al final), pero el problema en el código proporcionado es que * puede * devolver resultados falsos positivos. – Tigran

+0

@Tigran - Voy a cambiar la lógica para hacer xor en lugar de esto - sin embargo, quería verificar con usted re: rendimiento. ¿Cuándo esta operación tomará mucho tiempo, y hablamos milisegundos o algo más grande? Por el momento, no se espera que la colección de caminos sea de alguna manera grande, solo unos pocos elementos. ¿Debo preocuparme por ello? –

3

Como una pauta, el hash de un objeto debe ser el mismo durante toda la vida del objeto. Dejaría la función GetHashCode sola y no la sobreescribiría. El código hash solo se usa si desea colocar sus objetos en una tabla hash.

Debería leer el gran artículo de Eric Lippert sobre códigos hash en.NET: Guidelines and rules for GetHashCode.

Citado de que el artículo:

Directriz: el número entero devuelto por GetHashCode nunca debería cambiar la regla

: el número entero devuelto por GetHashCode nunca se debe cambiar mientras el objeto está contenido en una estructura de datos que depende en el código hash restante estable

Si el código hash de un objeto puede mutar mientras está en la tabla hash, entonces claramente el método Contiene deja de funcionar. Pones el objeto en el cubo # 5, lo muta, y cuando preguntas al conjunto si contiene el objeto mutado, busca en el cubo # 74 y no lo encuentra.

La función GetHashCode implementada no devolverá el mismo código hash durante la vida útil del objeto. Si usa esta función, tendrá problemas si agrega esos objetos a una tabla hash: el método Contains no funcionará.

6

El código de la respuesta de Jeppe Stig Nielsen funciona pero podría dar lugar a una gran cantidad de valores repetitivos de código hash. Digamos que estás mezclando una lista de entradas en el rango de 0-100, entonces tu código de hash estaría guardado entre 0 y 255. Esto genera muchas colisiones cuando se usa en un diccionario. Aquí es una versión mejorada:.

public override int GetHashCode() 
{ 
    int hc = 0; 
    if (Paths != null) 
    foreach (var p in Paths) { 
     hc ^= p.GetHashCode(); 
     hc = (hc << 7) | (hc >> (32 - 7)); //rotale hc to the left to swipe over all bits 
    } 
    return hc; 
} 

Este código será, al menos, involucrar a todos los bits con el tiempo a medida que más y más elementos son ordenadas en

+0

No estoy de acuerdo. Si el 'GetHashCode()' en la clase 'Route' está bien escrito (lo que tendremos que asumir), entonces la implementación de' my '' GetHashCode() 'en la clase' Routing' será genial. –

+2

Parece un GetHashCode no válido. Si pongo un objeto en una colección hash, y luego agrego un objeto a las Rutas, existe una gran posibilidad de que los Contienen nunca puedan encontrarlo ya que la cubeta habrá cambiado. El hash debe ser constante durante la vida del objeto. –

+1

¿Eh? GetHashCode debe cubrir todo el estado que desee utilizar para encontrar este objeto en una tabla hash. Si el operador no quería buscar en el camino, ¿por qué lo preguntó? Punto adicional: por supuesto, no puede cambiar el objeto una vez que se inserta, pero eso es * siempre * verdadero (incluso para un gethashcode como return 0, porque la igualdad no puede cambiar una vez que se inserta el objeto). Siguiente punto: Su punto es válido para todas las respuestas en esta pregunta que es poco probable. Creo que malinterpretaste la pregunta. – usr

Cuestiones relacionadas