Como você implementa GetHashCode para estrutura com duas strings, quando ambas as strings são intercambiáveis

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

  •  09-06-2019
  •  | 
  •  

Pergunta

Tenho uma estrutura em C#:

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

   public string str2
   {
     get;
     set;
   }   
}

A única regra é que UserInfo(str1="AA", str2="BB").Equals(UserInfo(str1="BB", str2="AA"))

Como substituir a função GetHashCode para esta estrutura?

Foi útil?

Solução

MSDN:

Uma função hash deve ter as seguintes propriedades:

  • Se dois objetos forem comparados como iguais, o GetHashCode método para cada objeto deve retornar o mesmo valor.No entanto, se dois objetos não forem comparados como iguais, o GetHashCode os métodos para os dois objetos não precisam retornar valores diferentes.
  • O GetHashCode O método para um objeto deve retornar consistentemente o mesmo código hash, desde que não haja nenhuma modificação no estado do objeto que determine o valor de retorno do objeto. Equals método.Observe que isso é verdade apenas para a execução atual de um aplicativo e que um código hash diferente pode ser retornado se o aplicativo for executado novamente.
  • Para obter o melhor desempenho, uma função hash deve gerar uma distribuição aleatória para todas as entradas.

Levar isso em consideração da maneira correta é:

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

^ pode ser substituído por outra operação comutativa

Outras dicas

Ver A resposta de Jon Skeet - operações binárias como ^ não são bons, muitas vezes geram hash de colisão!

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

Usar o operador '+' pode ser melhor do que usar '^', porque embora você queira explicitamente que ('AA', 'BB') e ('BB', 'AA') sejam explicitamente iguais, você pode não querer ( 'AA', 'AA') e ('BB', 'BB') sejam iguais (ou todos os pares iguais).

A regra 'o mais rápido possível' não é totalmente seguida nesta solução porque, no caso de nulos, isso executa um 'GetHashCode ()' na string vazia, em vez de retornar imediatamente uma constante conhecida, mas mesmo sem medir explicitamente, estou disposto arriscar um palpite de que a diferença não seria grande o suficiente para se preocupar, a menos que você espere muitos nulos.

  1. Como regra geral, uma maneira simples de gerar um código hash para uma classe é fazer um XOR de todos os campos de dados que podem participar da geração do código hash (tomando cuidado para verificar se há nulo, conforme apontado por outros).Isso também atende ao requisito (artificial?) De que os códigos hash para UserInfo("AA", "BB") e UserInfo("BB", "AA") sejam iguais.

  2. Se você puder fazer suposições sobre o uso de sua classe, talvez possa melhorar sua função hash.Por exemplo, se é comum que str1 e str2 sejam iguais, XOR pode não ser uma boa escolha.Mas se str1 e str2 representam, digamos, nome e sobrenome, XOR é provavelmente uma boa escolha.

Embora isto claramente não pretenda ser um exemplo do mundo real, pode valer a pena salientar que:- Este é provavelmente um mau exemplo de uso de uma estrutura:Uma estrutura normalmente deveria ter semântica de valor, o que não parece ser o caso aqui.- Usar propriedades com setters para gerar um código hash também está causando problemas.

Um simples em geral maneira é fazer isso:

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

A menos que você tenha requisitos rígidos de desempenho, este é o mais fácil que consigo pensar e frequentemente uso esse método quando preciso de uma chave composta.Ele lida com o null casos muito bem e não causarão (m) nenhuma colisão de hash (em geral).Se você espera '/' em suas strings, basta escolher outro separador que você não espera.

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

Seguindo as linhas que o ReSharper está sugerindo:

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 é um primo de tamanho suficiente para fazer com que a variável de resultado transborde e misture um pouco os bits do hash, proporcionando uma melhor distribuição dos códigos hash.Caso contrário, não há nada de especial em 397 que o distinga de outros primos da mesma magnitude.

Ah, sim, como Gary Shutler apontou:

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

Pode transbordar.Você pode tentar transmitir por muito tempo, conforme sugerido por Artem, ou pode colocar a declaração na palavra-chave desmarcada:

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

Experimente este:

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

Muitas possibilidades.Por exemplo.

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

Talvez algo como str1.GetHashCode() + str2.GetHashCode()?ou (str1.GetHashCode() + str2.GetHashCode())/2?Desta forma, seria o mesmo, independentemente de str1 e str2 serem trocados....

Classifique-os e concatene-os:

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

O resultado do GetHashCode deveria ser:

  1. O mais rápido possível.
  2. Tão único quanto possível.

Tendo isso em mente, eu diria algo assim:

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: Esqueci os nulos.Código corrigido.

Muito complicado e esquece nulos, etc.Isso é usado para coisas como bucketing, para que você possa fazer 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;

Isso é tendencioso ao assumir que str1 provavelmente não será comum em uma proporção incomumente grande de casos.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top