¿Cómo se implementa GetHashCode para una estructura con dos cadenas, cuando ambas cadenas son intercambiables?

StackOverflow https://stackoverflow.com/questions/70303

  •  09-06-2019
  •  | 
  •  

Pregunta

Tengo una estructura en C#:

public struct UserInfo
{
   public string str1
   {
     get;
     set;
   }

   public string str2
   {
     get;
     set;
   }   
}

La única regla es que UserInfo(str1="AA", str2="BB").Equals(UserInfo(str1="BB", str2="AA"))

¿Cómo anular la función GetHashCode para esta estructura?

¿Fue útil?

Solución

MSDN:

Una función hash debe tener las siguientes propiedades:

  • Si dos objetos se comparan como iguales, el GetHashCode El método para cada objeto debe devolver el mismo valor.Sin embargo, si dos objetos no se comparan como iguales, el GetHashCode Los métodos para los dos objetos no tienen que devolver valores diferentes.
  • El GetHashCode El método para un objeto debe devolver consistentemente el mismo código hash siempre que no haya ninguna modificación en el estado del objeto que determine el valor de retorno del objeto. Equals método.Tenga en cuenta que esto es cierto solo para la ejecución actual de una aplicación y que se puede devolver un código hash diferente si la aplicación se ejecuta nuevamente.
  • Para obtener el mejor rendimiento, una función hash debe generar una distribución aleatoria para todas las entradas.

Teniendolo en cuenta la forma correcta es:

return str1.GetHashCode() ^ str2.GetHashCode() 

^ se puede sustituir por otra operación conmutativa

Otros consejos

Ver La respuesta de Jon Skeet - operaciones binarias como ^ no son buenos, ¡a menudo generarán hash en colisión!

public override int GetHashCode()
{
    unchecked
    {
        return (str1 ?? String.Empty).GetHashCode() +
            (str2 ?? String.Empty).GetHashCode();
    }
}

Usar el operador '+' puede ser mejor que usar '^', porque aunque desea explícitamente que ('AA', 'BB') y ('BB', 'AA') sean explícitamente iguales, es posible que no desee ( 'AA', 'AA') y ('BB', 'BB') son iguales (o todos pares iguales).

La regla 'lo más rápido posible' no se cumple por completo en esta solución porque en el caso de valores nulos, esto realiza un 'GetHashCode()' en la cadena vacía en lugar de devolver inmediatamente una constante conocida, pero incluso sin medir explícitamente, estoy dispuesto arriesgarse a suponer que la diferencia no sería lo suficientemente grande como para preocuparse a menos que espere muchos nulos.

  1. Como regla general, una forma sencilla de generar un código hash para una clase es XOR todos los campos de datos que pueden participar en la generación del código hash (teniendo cuidado de verificar si hay valores nulos como lo señalaron otros).Esto también cumple con el requisito (¿artificial?) de que los códigos hash para UserInfo("AA", "BB") y UserInfo("BB", "AA") sean los mismos.

  2. Si puede hacer suposiciones sobre el uso de su clase, quizás pueda mejorar su función hash.Por ejemplo, si es común que str1 y str2 sean iguales, es posible que XOR no sea una buena opción.Pero si str1 y str2 representan, digamos, nombre y apellido, XOR probablemente sea una buena opción.

Aunque claramente este no pretende ser un ejemplo del mundo real, puede valer la pena señalar que:- Este es probablemente un mal ejemplo del uso de una estructura:Una estructura normalmente debería tener una semántica de valores, lo que no parece ser el caso aquí.- El uso de propiedades con definidores para generar un código hash también genera problemas.

Un simple general manera es hacer esto:

return string.Format("{0}/{1}", str1, str2).GetHashCode();

A menos que tenga requisitos de rendimiento estrictos, este es el método más fácil que se me ocurre y suelo utilizar este método cuando necesito una clave compuesta.Se encarga de null Los casos están bien y no causarán (m) ninguna colisión de hash (en general).Si espera '/' en sus cadenas, simplemente elija otro separador que no espere.

public override int GetHashCode()   
{       
    unchecked      
    {           
        return(str1 != null ? str1.GetHashCode() : 0) ^ (str2 != null ? str2.GetHashCode() : 0);       
    }   
}

Siguiendo la línea que sugiere ReSharper:

public int GetHashCode()
{
    unchecked
    {
        int hashCode;

        // String properties
        hashCode = (hashCode * 397) ^ (str1!= null ? str1.GetHashCode() : 0);
        hashCode = (hashCode * 397) ^ (str2!= null ? str1.GetHashCode() : 0);

        // int properties
        hashCode = (hashCode * 397) ^ intProperty;
        return hashCode;
    }
}

397 es un número primo de tamaño suficiente para hacer que la variable de resultado se desborde y mezcle un poco los bits del hash, lo que proporciona una mejor distribución de los códigos hash.Por lo demás, no hay nada especial en 397 que lo distinga de otros primos de la misma magnitud.

Ah, sí, como señaló Gary Shutler:

return str1.GetHashCode() + str2.GetHashCode();

Puede desbordarse.Puedes intentar transmitir el tiempo que sugirió Artem, o puedes rodear la declaración con la palabra clave sin marcar:

return unchecked(str1.GetHashCode() + str2.GetHashCode());

Pruebe este:

(((long)str1.GetHashCode()) + ((long)str2.GetHashCode())).GetHashCode()

Muchas posibilidades.P.ej.

return str1.GetHashCode() ^ str1.GetHashCode()

¿Quizás algo como str1.GetHashCode() + str2.GetHashCode()?o (str1.GetHashCode() + str2.GetHashCode()) / 2?De esta manera sería lo mismo independientemente de si se intercambian str1 y str2....

Ordenarlos y luego concatenarlos:

return ((str1.CompareTo(str2) < 1) ? str1 + str2 : str2 + str1)
    .GetHashCode();

Se supone que el resultado de GetHashCode es:

  1. Tan rápido como sea posible.
  2. Lo más único posible.

Teniendo esto en cuenta, yo elegiría algo como esto:

if (str1 == null)
    if (str2 == null)
        return 0;
    else
       return str2.GetHashCode();
else
    if (str2 == null)
        return str1.GetHashCode();
    else
       return ((ulong)str1.GetHashCode() | ((ulong)str2.GetHashCode() << 32)).GetHashCode();

Editar: Olvidé los nulos.Código arreglado.

Demasiado complicado y olvida nulos, etc.Esto se usa para cosas como agrupar, por lo que puedes salirte con la tuya con algo como

if (null != str1) {
    return str1.GetHashCode();
}
if (null != str2) {
    return str2.GetHashCode();
}
//Not sure what you would put here, some constant value will do
return 0;

Esto está sesgado al suponer que no es probable que str1 sea común en una proporción inusualmente grande de casos.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top