Dada a seguinte classe
public class Foo
{
public int FooId { get; set; }
public string FooName { get; set; }
public override bool Equals(object obj)
{
Foo fooItem = obj as Foo;
if (fooItem == null)
{
return false;
}
return fooItem.FooId == this.FooId;
}
public override int GetHashCode()
{
// Which is preferred?
return base.GetHashCode();
//return this.FooId.GetHashCode();
}
}
Eu substituí o Equals
método porque Foo
representa uma linha para a Foo
tabela s. Qual é o método preferido para substituir o GetHashCode
?
Por que é importante substituir GetHashCode
?
c#
overriding
hashcode
David Basarab
fonte
fonte
Respostas:
Sim, é importante se o seu item for usado como chave em um dicionário ou
HashSet<T>
etc - pois é usado (na ausência de um costumeIEqualityComparer<T>
) para agrupar itens em baldes. Se o código hash para dois itens não corresponder, eles nunca poderão ser considerados iguais ( iguais simplesmente nunca serão chamados).O método GetHashCode () deve refletir a
Equals
lógica; as regras são:Equals(...) == true
), elas devem retornar o mesmo valor paraGetHashCode()
GetHashCode()
for igual, é não necessário para que eles sejam o mesmo; isso é uma colisão eEquals
será chamado para ver se é uma igualdade real ou não.Nesse caso, parece que "
return FooId;
" é umaGetHashCode()
implementação adequada . Se você estiver testando várias propriedades, é comum combiná-las usando o código abaixo, para reduzir colisões diagonais (ou seja, para que elenew Foo(3,5)
tenha um código hash diferentenew Foo(5,3)
):Ah - por conveniência, você também pode considerar fornecer
==
e!=
operadores ao substituirEquals
eGetHashCode
.Uma demonstração do que acontece quando você comete um erro está aqui .
fonte
Na verdade, é muito difícil de implementar
GetHashCode()
corretamente porque, além das regras que Marc já mencionou, o código de hash não deve ser alterado durante a vida útil de um objeto. Portanto, os campos usados para calcular o código de hash devem ser imutáveis.Finalmente encontrei uma solução para esse problema quando estava trabalhando com o NHibernate. Minha abordagem é calcular o código de hash a partir do ID do objeto. O ID pode ser definido apenas pelo construtor, portanto, se você deseja alterar o ID, o que é muito improvável, é necessário criar um novo objeto que tenha um novo ID e, portanto, um novo código de hash. Essa abordagem funciona melhor com GUIDs porque você pode fornecer um construtor sem parâmetros que gera aleatoriamente um ID.
fonte
Ao substituir o Equals, você basicamente afirma que é quem sabe como comparar duas instâncias de um determinado tipo; portanto, é provável que você seja o melhor candidato a fornecer o melhor código de hash.
Este é um exemplo de como o ReSharper grava uma função GetHashCode () para você:
Como você pode ver, apenas tenta adivinhar um bom código de hash com base em todos os campos da classe, mas como você conhece o domínio ou os intervalos de valores do seu objeto, você ainda pode fornecer um melhor.
fonte
0 ^ a = a
, então0 ^ m_someVar1 = m_someVar1
. Ele também pode definir o valor inicial deresult
comom_someVar1
.Não se esqueça de verificar o parâmetro obj
null
ao substituirEquals()
. E também compare o tipo.A razão para isso é:
Equals
deve retornar false em comparação comnull
. Consulte também http://msdn.microsoft.com/en-us/library/bsc2ak47.aspxfonte
obj
realmente igual a,this
independentemente de como Equals () da classe básica foi chamada.fooItem
para o topo e, em seguida, verificar se há nulo terá um desempenho melhor no caso de um tipo nulo ou errado.obj as Foo
seria inválido.E se:
fonte
string.Format
. Outro nerd que eu já vi énew { prop1, prop2, prop3 }.GetHashCode()
. Não é possível comentar qual seria mais lento entre esses dois. Não abuse das ferramentas.{ prop1="_X", prop2="Y", prop3="Z" }
e{ prop1="", prop2="X_Y", prop3="Z_" }
. Você provavelmente não quer isso.Temos dois problemas para resolver.
Você não pode fornecer um sentido
GetHashCode()
se qualquer campo no objeto puder ser alterado. Muitas vezes, um objeto NUNCA será usado em uma coleção que dependeGetHashCode()
. Portanto, o custo de implementaçãoGetHashCode()
geralmente não vale a pena ou não é possível.Se alguém colocar seu objeto em uma coleção que chama
GetHashCode()
e você substituir,Equals()
sem fazer com queGetHashCode()
se comporte da maneira correta, essa pessoa poderá passar dias rastreando o problema.Portanto, por padrão, eu faço.
fonte
GetHashCode
função de forma que dois objetos iguais retornem o mesmo código de hash;return 24601;
ereturn 8675309;
seriam implementações válidas deGetHashCode
. O desempenho deDictionary
apenas será decente quando o número de itens for pequeno e ficará muito ruim se o número de itens for grande, mas funcionará corretamente em qualquer caso.Isso ocorre porque a estrutura exige que dois objetos iguais tenham o mesmo código hash. Se você substituir o método equals para fazer uma comparação especial de dois objetos e os dois objetos forem considerados iguais pelo método, o código de hash dos dois objetos também deverá ser o mesmo. (Dicionários e hashtables baseiam-se nesse princípio).
fonte
Apenas para adicionar as respostas acima:
Se você não substituir Igual, o comportamento padrão é que as referências dos objetos sejam comparadas. O mesmo se aplica ao código de hash - a implementação padrão geralmente é baseada no endereço de memória da referência. Como você substituiu Equals, significa que o comportamento correto é comparar o que você implementou em Equals e não as referências; portanto, você deve fazer o mesmo com o código de hash.
Os clientes da sua classe esperam que o código hash tenha uma lógica semelhante ao método equals, por exemplo, os métodos linq que usam um IEqualityComparer comparam primeiro os códigos hash e, somente se forem iguais, eles compararão o método Equals (), que pode ser mais caro para executar, se não implementarmos o código de hash, o objeto igual provavelmente terá códigos de hash diferentes (porque eles têm um endereço de memória diferente) e será determinado incorretamente como não igual (Equals () nem atingirá).
Além disso, exceto o problema de que talvez você não consiga encontrar seu objeto se o tiver usado em um dicionário (porque ele foi inserido por um código de hash e quando você o procura, o código de hash padrão provavelmente será diferente e novamente Equals () nem será chamado, como Marc Gravell explica em sua resposta, você também introduzirá uma violação do dicionário ou do conceito de hashset que não deve permitir chaves idênticas - você já declarou que esses objetos são essencialmente os mesmos quando substituem o Igual para não deseja que ambos sejam chaves diferentes em uma estrutura de dados que suponha ter uma chave única, mas como eles têm um código de hash diferente, a chave "mesma" será inserida como uma chave diferente.
fonte
O código hash é usado para coleções baseadas em hash, como Dictionary, Hashtable, HashSet etc. O objetivo desse código é pré-classificar rapidamente objetos específicos, colocando-os em um grupo específico (bucket). Essa pré-classificação ajuda tremendamente na localização desse objeto quando você precisa recuperá-lo novamente da coleção de hash porque o código precisa procurar seu objeto em apenas um bloco em vez de em todos os objetos que ele contém. A melhor distribuição dos códigos de hash (melhor exclusividade) e a recuperação mais rápida. Na situação ideal em que cada objeto tem um código de hash exclusivo, descobrir que é uma operação O (1). Na maioria dos casos, ele se aproxima de O (1).
fonte
Não é necessariamente importante; isso depende do tamanho das suas coleções e dos seus requisitos de desempenho e se sua classe será usada em uma biblioteca na qual você talvez não conheça os requisitos de desempenho. Sei com frequência que meus tamanhos de coleção não são muito grandes e meu tempo é mais valioso do que alguns microssegundos de desempenho obtidos com a criação de um código hash perfeito; então (para me livrar do aviso irritante do compilador) eu simplesmente uso:
(É claro que eu poderia usar um #pragma para desativar o aviso também, mas prefiro assim.)
Quando você está na posição que você não precisa do desempenho de todas as questões mencionadas por outros aqui se aplicam, naturalmente. Mais importante - caso contrário, você obterá resultados incorretos ao recuperar itens de um conjunto de hash ou dicionário: o código de hash não deve variar com o tempo de vida de um objeto (mais precisamente, durante o tempo em que o código de hash é necessário, como durante a uma chave em um dicionário): por exemplo, o seguinte está errado, pois Value é público e, portanto, pode ser alterado externamente para a classe durante o tempo de vida da instância, portanto, você não deve usá-lo como base para o código de hash:
Por outro lado, se o Valor não puder ser alterado, você poderá usar:
fonte
Você sempre deve garantir que, se dois objetos forem iguais, conforme definido por Equals (), eles retornem o mesmo código de hash. Como alguns dos outros comentários afirmam, em teoria isso não é obrigatório se o objeto nunca for usado em um contêiner baseado em hash como o HashSet ou o Dictionary. Eu aconselho você a sempre seguir esta regra. O motivo é simplesmente porque é muito fácil para alguém alterar uma coleção de um tipo para outro com a boa intenção de realmente melhorar o desempenho ou apenas transmitir a semântica do código de uma maneira melhor.
Por exemplo, suponha que mantemos alguns objetos em uma lista. Algum tempo depois, alguém realmente percebe que um HashSet é uma alternativa muito melhor devido às melhores características de pesquisa, por exemplo. É aí que podemos ter problemas. A lista usaria internamente o comparador de igualdade padrão para o tipo que significa Equals no seu caso, enquanto o HashSet usa GetHashCode (). Se os dois se comportarem de maneira diferente, o mesmo acontecerá com o seu programa. E lembre-se de que esses problemas não são os mais fáceis de solucionar.
Resumi esse comportamento com algumas outras armadilhas GetHashCode () em uma postagem do blog onde você pode encontrar mais exemplos e explicações.
fonte
A partir do
.NET 4.7
método preferido de substituiçãoGetHashCode()
é mostrado abaixo. Se você estiver direcionando versões .NET mais antigas, inclua o pacote de nuget System.ValueTuple .Em termos de desempenho, esse método superará a maioria das implementações de código hash composto . O ValueTuple é um
struct
para que não haja lixo, e o algoritmo subjacente é o mais rápido possível.fonte
Entendo que o GetHashCode () original retorna o endereço de memória do objeto, por isso é essencial substituí-lo se você deseja comparar dois objetos diferentes.
EDITADO: Isso estava incorreto, o método GetHashCode () original não pode garantir a igualdade de 2 valores. Embora objetos iguais retornem o mesmo código de hash.
fonte
Abaixo, usar a reflexão me parece uma opção melhor, considerando as propriedades públicas, pois com isso você não precisa se preocupar com a adição / remoção de propriedades (embora não seja um cenário tão comum). Também achei que esse desempenho estava melhor (tempo comparado usando o cronômetro da Diagonistics).
fonte