Como funciona a implementação padrão GetHashCode()
? E ele lida com estruturas, classes, matrizes etc. de maneira eficiente e suficientemente boa?
Estou tentando decidir em quais casos devo embalar o meu e em quais casos posso confiar com segurança na implementação padrão para fazer o bem. Não quero reinventar a roda, se possível.
.net
hash
gethashcode
Fung
fonte
fonte
GetHashCode()
substituído) usandoSystem.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj)
Respostas:
InternalGetHashCode é mapeado para uma função ObjectNative :: GetHashCode no CLR, que se parece com isso:
A implementação completa do GetHashCodeEx é bastante grande, portanto, é mais fácil vincular apenas ao código-fonte C ++ .
fonte
string
substituiGetHashCode
. Por outro lado, suponha que você queira manter uma contagem de quantas vezes vários controles processamPaint
eventos. Você pode usar umDictionary<Object, int[]>
(todos osint[]
armazenados conteriam exatamente um item).Para uma classe, os padrões são essencialmente a igualdade de referência, e isso geralmente é bom. Ao escrever uma estrutura, é mais comum substituir a igualdade (principalmente para evitar o boxe), mas é muito raro você escrever uma estrutura de qualquer maneira!
Ao substituir a igualdade, você deve sempre ter uma correspondência
Equals()
eGetHashCode()
(por exemplo, para dois valores, seEquals()
retorna true, eles devem retornar o mesmo código hash, mas o inverso não é necessário) - e é comum também fornecer==
/!=
operadores e, frequentemente, implementarIEquatable<T>
também.Para gerar o código hash, é comum usar uma soma fatorada, pois isso evita colisões em valores emparelhados - por exemplo, para um hash básico de 2 campos:
Isso tem a vantagem de que:
etc - o que pode ser comum se você estiver usando uma soma não ponderada, ou xor (
^
), etc.fonte
unchecked
. Felizmente,unchecked
é o padrão em C #, mas seria melhor torná-lo explícito; editadoA documentação do
GetHashCode
método para Object diz que "a implementação padrão deste método não deve ser usada como um identificador de objeto exclusivo para fins de hash". e o valor de ValueType diz "Se você chamar o método GetHashCode do tipo derivado, é provável que o valor de retorno não seja adequado para uso como chave em uma tabela de hash". .Os tipos de dados básicos, como
byte
,short
,int
,long
,char
estring
implementar um método bom GetHashCode. Algumas outras classes e estruturas, comoPoint
por exemplo, implementam umGetHashCode
método que pode ou não ser adequado às suas necessidades específicas. Você só precisa experimentá-lo para ver se é bom o suficiente.A documentação para cada classe ou estrutura pode dizer se substitui a implementação padrão ou não. Se não o substituir, você deve usar sua própria implementação. Para quaisquer classes ou estruturas criadas por você, nas quais é necessário usar o
GetHashCode
método, você deve fazer sua própria implementação que usa os membros apropriados para calcular o código de hash.fonte
Como não consegui encontrar uma resposta que explique por que devemos substituir
GetHashCode
eEquals
para estruturas personalizadas e por que a implementação padrão "provavelmente não é adequada para uso como chave em uma tabela de hash", deixarei um link para este blog. post , o que explica por que, com um exemplo real de um problema que aconteceu.Eu recomendo a leitura do post inteiro, mas aqui está um resumo (ênfase e esclarecimentos adicionados).
Motivo pelo qual o hash padrão para estruturas é lento e não muito bom:
Problema do mundo real descrito no post:
Portanto, para responder à pergunta "em quais casos eu devo compactar minha conta e em quais casos posso confiar com segurança na implementação padrão", pelo menos no caso de estruturas , você deve substituir
Equals
eGetHashCode
sempre que sua estrutura personalizada puder ser usada como um digite uma tabela de hash ouDictionary
.Eu também recomendaria implementar
IEquatable<T>
neste caso, para evitar o boxe.Como as outras respostas disseram, se você está escrevendo uma classe , o hash padrão usando a igualdade de referência geralmente é bom, então eu não me incomodaria nesse caso, a menos que você precise substituir
Equals
(então você deve substituir emGetHashCode
conformidade).fonte
De um modo geral, se você estiver substituindo Igual, você deseja substituir GetHashCode. A razão para isso é porque ambos são usados para comparar a igualdade de sua classe / estrutura.
Igual é usado ao verificar Foo A, B;
se (A == B)
Como sabemos que o ponteiro provavelmente não corresponde, podemos comparar os membros internos.
GetHashCode é geralmente usado por tabelas de hash. O código hash gerado por sua classe deve sempre ser o mesmo para um estado de classe.
Eu normalmente faço,
Alguns dirão que o código hash deve ser calculado apenas uma vez por vida útil do objeto, mas não concordo com isso (e provavelmente estou errado).
Usando a implementação padrão fornecida pelo objeto, a menos que você tenha a mesma referência a uma de suas classes, elas não serão iguais entre si. Substituindo Equals e GetHashCode, você pode relatar a igualdade com base em valores internos, e não na referência de objetos.
fonte
Se você está apenas lidando com POCOs, pode usar este utilitário para simplificar um pouco sua vida:
...
fonte