¿Cuál es el mejor algoritmo para un System.Object.GetHashCode reemplazado?

En el método .NET System.Object.GetHashCode se usa en muchos lugares, a través de las bibliotecas de clases base de .NET. Especialmente cuando se encuentran elementos en una colección rápidamente o para determinar la igualdad. ¿Existe algún algoritmo / práctica recomendada sobre cómo implementar la anulación de GetHashCode para mis clases personalizadas, para no degradar el rendimiento?

Suelo ir con algo parecido a la implementación dada en la fabulosa Java efectiva de Josh Bloch. Es rápido y crea un hash muy bueno que no es probable que cause colisiones. Elige dos números primos diferentes, por ejemplo, 17 y 23, y haz:

 public override int GetHashCode() { unchecked // Overflow is fine, just wrap { int hash = 17; // Suitable nullity checks etc, of course :) hash = hash * 23 + field1.GetHashCode(); hash = hash * 23 + field2.GetHashCode(); hash = hash * 23 + field3.GetHashCode(); return hash; } } 

Como se señala en los comentarios, es posible que, en su lugar, sea mejor elegir un primo grande para multiplicar. Aparentemente 486187739 es bueno … y aunque la mayoría de los ejemplos que he visto con números pequeños tienden a usar números primos, hay al menos algoritmos similares donde a menudo se usan números no primos. En el ejemplo que no es bastante FNV más adelante, por ejemplo, he usado números que aparentemente funcionan bien, pero el valor inicial no es primo. (Sin embargo, la constante de multiplicación es primordial. No sé qué tan importante es eso).

Esto es mejor que la práctica común de XOR ing hashcodes por dos razones principales. Supongamos que tenemos un tipo con dos campos int :

 XorHash(x, x) == XorHash(y, y) == 0 for all x, y XorHash(x, y) == XorHash(y, x) for all x, y 

Por cierto, el algoritmo anterior es el utilizado actualmente por el comstackdor C # para tipos anónimos.

Esta página ofrece bastantes opciones. Creo que para la mayoría de los casos, lo anterior es “lo suficientemente bueno” y es increíblemente fácil de recordar y acertar. La alternativa de FNV es similarmente simple, pero usa constantes diferentes y XOR lugar de ADD como una operación de combinación. Se parece más al código siguiente, pero el algoritmo FNV normal funciona en bytes individuales, por lo que sería necesario modificar para realizar una iteración por byte, en lugar de un valor hash de 32 bits. FNV también está diseñado para longitudes de datos variables, mientras que la forma en que lo estamos usando aquí es siempre para el mismo número de valores de campo. Los comentarios sobre esta respuesta sugieren que el código aquí en realidad no funciona tan bien (en el caso de muestra probado) como el enfoque de adición anterior.

 // Note: Not quite FNV! public override int GetHashCode() { unchecked // Overflow is fine, just wrap { int hash = (int) 2166136261; // Suitable nullity checks etc, of course :) hash = (hash * 16777619) ^ field1.GetHashCode(); hash = (hash * 16777619) ^ field2.GetHashCode(); hash = (hash * 16777619) ^ field3.GetHashCode(); return hash; } } 

Tenga en cuenta que una cosa a tener en cuenta es que, idealmente, debe evitar que cambie su estado sensible a la igualdad (y por lo tanto, el código hash) después de agregarlo a una colección que depende del código hash.

Según la documentación :

Puede anular GetHashCode para tipos de referencia inmutables. En general, para los tipos de referencia mutables, debe anular GetHashCode solo si:

  • Puede calcular el código hash de los campos que no son mutables; o
  • Puede asegurarse de que el código hash de un objeto mutable no cambie mientras el objeto está contenido en una colección que se basa en su código hash.

Tipo anónimo

Microsoft ya proporciona un buen generador genérico HashCode: simplemente copie los valores de su propiedad / campo en un tipo anónimo y cópielo:

 new { PropA, PropB, PropC, PropD }.GetHashCode(); 

Esto funcionará para cualquier cantidad de propiedades. No usa el boxeo. Solo usa el algoritmo ya implementado en el marco para tipos anónimos.

ValueTuple – Actualización para C # 7

Como @cactuaroid menciona en los comentarios, se puede usar una tupla de valor. Esto ahorra algunas pulsaciones de teclas y, lo que es más importante, se ejecuta puramente en la stack (sin Basura):

 (PropA, PropB, PropC, PropD).GetHashCode(); 

(Nota: la técnica original que usa tipos anónimos parece crear un objeto en el montón, es decir, basura, ya que los tipos anónimos se implementan como clases, aunque esto podría ser optimizado por el comstackdor. Sería interesante comparar estas opciones, pero el la opción de tupla debe ser superior).

Aquí está mi ayudante hashcode.
Su ventaja es que utiliza argumentos de tipo genérico y, por lo tanto, no causará el boxeo:

 public static class HashHelper { public static int GetHashCode(T1 arg1, T2 arg2) { unchecked { return 31 * arg1.GetHashCode() + arg2.GetHashCode(); } } public static int GetHashCode(T1 arg1, T2 arg2, T3 arg3) { unchecked { int hash = arg1.GetHashCode(); hash = 31 * hash + arg2.GetHashCode(); return 31 * hash + arg3.GetHashCode(); } } public static int GetHashCode(T1 arg1, T2 arg2, T3 arg3, T4 arg4) { unchecked { int hash = arg1.GetHashCode(); hash = 31 * hash + arg2.GetHashCode(); hash = 31 * hash + arg3.GetHashCode(); return 31 * hash + arg4.GetHashCode(); } } public static int GetHashCode(T[] list) { unchecked { int hash = 0; foreach (var item in list) { hash = 31 * hash + item.GetHashCode(); } return hash; } } public static int GetHashCode(IEnumerable list) { unchecked { int hash = 0; foreach (var item in list) { hash = 31 * hash + item.GetHashCode(); } return hash; } } ///  /// Gets a hashcode for a collection for that the order of items /// does not matter. /// So {1, 2, 3} and {3, 2, 1} will get same hash code. ///  public static int GetHashCodeForOrderNoMatterCollection( IEnumerable list) { unchecked { int hash = 0; int count = 0; foreach (var item in list) { hash += item.GetHashCode(); count++; } return 31 * hash + count.GetHashCode(); } } ///  /// Alternative way to get a hashcode is to use a fluent /// interface like this:
/// return 0.CombineHashCode(field1).CombineHashCode(field2). /// CombineHashCode(field3); ///
public static int CombineHashCode
(this int hashCode, T arg) { unchecked { return 31 * hashCode + arg.GetHashCode(); } }

También tiene un método de extensión para proporcionar una interfaz fluida, por lo que puede usarlo así:

 public override int GetHashCode() { return HashHelper.GetHashCode(Manufacturer, PartN, Quantity); } 

o así:

 public override int GetHashCode() { return 0.CombineHashCode(Manufacturer) .CombineHashCode(PartN) .CombineHashCode(Quantity); } 

Tengo una clase Hashing en la biblioteca Helper que la uso para este propósito.

 ///  /// This is a simple hashing function from Robert Sedgwicks Hashing in C book. /// Also, some simple optimizations to the algorithm in order to speed up /// its hashing process have been added. from: www.partow.net ///  /// array of objects, parameters combination that you need /// to get a unique hash code for them /// Hash code public static int RSHash(params object[] input) { const int b = 378551; int a = 63689; int hash = 0; // If it overflows then just wrap around unchecked { for (int i = 0; i < input.Length; i++) { if (input[i] != null) { hash = hash * a + input[i].GetHashCode(); a = a * b; } } } return hash; } 

Entonces, simplemente puedes usarlo como:

 public override int GetHashCode() { return Hashing.RSHash(_field1, _field2, _field3); } 

No evalué su rendimiento, por lo que cualquier comentario es bienvenido.

Aquí está mi clase de ayuda usando la implementación de Jon Skeet .

 public static class HashCode { public const int Start = 17; public static int Hash(this int hash, T obj) { var h = EqualityComparer.Default.GetHashCode(obj); return unchecked((hash * 31) + h); } } 

Uso:

 public override int GetHashCode() { return HashCode.Start .Hash(_field1) .Hash(_field2) .Hash(_field3); } 

Si desea evitar escribir un método de extensión para System.Int32:

 public struct HashCode { private readonly int _value; public HashCode(int value) => _value = value; public static HashCode Start { get; } = new HashCode(17); public static implicit operator int(HashCode hash) => hash._value; public HashCode Hash(T obj) { var h = EqualityComparer.Default.GetHashCode(obj); return unchecked(new HashCode((_value * 31) + h)); } public override int GetHashCode() => _value; } 

Sigue siendo genérico, todavía evita la asignación de montones y se usa exactamente de la misma manera:

 public override int GetHashCode() { // This time `HashCode.Start` is not an `Int32`, it's a `HashCode` instance. // And the result is implicitly converted to `Int32`. return HashCode.Start .Hash(_field1) .Hash(_field2) .Hash(_field3); } 

Actualización después del comentario de Martin:

obj != null causó el boxeo, así que cambié al comparador predeterminado.

  • Vea esta respuesta con respecto al rendimiento predeterminado del comparador.
  • Vea esta pregunta para una discusión sobre los códigos hash de valores nulos.

Editar (mayo de 2018):

EqualityComparer.Default getter ahora es un JIT intrínseco: Stephen Toub menciona la solicitud de extracción en esta publicación de blog .

En la mayoría de los casos en que Equals () compara múltiples campos, realmente no importa si Hashash () hash en un campo o en muchos. Solo tienes que asegurarte de que el cálculo del hash es realmente económico ( sin asignaciones , por favor) y rápido ( sin cálculos pesados y ciertamente sin conexiones a la base de datos) y proporciona una buena distribución.

El levantamiento de objetos pesados ​​debe ser parte del método Equals (); el hash debería ser una operación muy económica para poder llamar a Equals () en el menor número posible de elementos.

Y un último consejo: no confíe en que GetHashCode () se mantendrá estable durante múltiples ejecuciones de aplicaciones . Muchos tipos de .Net no garantizan que sus códigos hash permanezcan iguales después de un reinicio, por lo que solo debe usar el valor de GetHashCode () para estructuras de datos de memoria.

Hasta hace poco mi respuesta hubiera sido muy similar a la de Jon Skeet aquí. Sin embargo, recientemente comencé un proyecto que utilizaba tablas hash de power-of-two, que son tablas hash donde el tamaño de la tabla interna es 8, 16, 32, etc. Hay una buena razón para favorecer los tamaños de números primos, pero hay son algunas ventajas para los tamaños de potencia de dos también.

Y es bastante desagradable. Entonces, después de un poco de experimentación e investigación, comencé a volver a mezclar mis hashes con lo siguiente:

 public static int ReHash(int source) { unchecked { ulong c = 0xDEADBEEFDEADBEEF + (ulong)source; ulong d = 0xE2ADBEEFDEADBEEF ^ c; ulong a = d += c = c << 15 | c >> -15; ulong b = a += d = d << 52 | d >> -52; c ^= b += a = a << 26 | a >> -26; d ^= c += b = b << 51 | b >> -51; a ^= d += c = c << 28 | c >> -28; b ^= a += d = d << 9 | d >> -9; c ^= b += a = a << 47 | a >> -47; d ^= c += b << 54 | b >> -54; a ^= d += c << 32 | c >> 32; a += d << 25 | d >> -25; return (int)(a >> 1); } } 

Y luego mi tabla de hash de poder de dos ya no masticaba.

Esto me molestó, porque lo anterior no debería funcionar. O más precisamente, no debería funcionar a menos que GetHashCode() original GetHashCode() fuera pobre de una manera muy particular.

Volver a mezclar un código hash no puede mejorar un gran código hash, porque el único efecto posible es que presentamos algunas colisiones más.

Volver a mezclar un código hash no puede mejorar un terrible código hash, porque el único efecto posible es que cambiemos, por ejemplo, una gran cantidad de colisiones en el valor 53 a un gran número de valor 18,3487,291.

Volver a mezclar un código hash solo puede mejorar un código hash que al menos sirvió para evitar colisiones absolutas en todo su rango (2 32 valores posibles) pero mal para evitar colisiones cuando se moduló para uso real en una tabla hash. Si bien el módulo más simple de una tabla de potencia de dos lo hizo más aparente, también estaba teniendo un efecto negativo con las tablas de números primos más comunes, que simplemente no era tan obvio (el trabajo adicional en la reconstrucción superaría el beneficio , pero el beneficio aún estaría allí).

Editar: También estaba usando el direccionamiento abierto, lo que también habría aumentado la sensibilidad a la colisión, tal vez más que el hecho de que era potencia de dos.

Y bueno, fue inquietante lo mucho que las implementaciones de string.GetHashCode() en .NET (o estudiar aquí ) podrían mejorarse de esta manera (en el orden de las pruebas que se ejecutan entre 20 y 30 veces más rápido debido a la menor cantidad de colisiones) y más inquietantes mucho mis propios códigos hash podrían mejorarse (mucho más que eso).

Todas las implementaciones de GetHashCode () que codifiqué en el pasado, y de hecho utilizadas como base de las respuestas en este sitio, fueron mucho peores de lo que hubiera sido . La mayoría de las veces fue “lo suficientemente bueno” para muchos de los usos, pero yo quería algo mejor.

Así que puse ese proyecto a un lado (de todos modos era un proyecto favorito) y comencé a buscar rápidamente cómo generar un código hash bueno y bien distribuido en .NET.

Al final, decidí portar SpookyHash a .NET. De hecho, el código anterior es una versión rápida de SpookyHash para producir una salida de 32 bits a partir de una entrada de 32 bits.

Ahora, SpookyHash no es una buena pieza de código rápida y fácil de recordar. Mi punto de inflexión es aún menor porque lo he alineado a mano para obtener una mejor velocidad *. Pero para eso sirve la reutilización de código.

Luego puse ese proyecto a un lado, porque así como el proyecto original había producido la pregunta de cómo producir un mejor código hash, entonces ese proyecto produjo la pregunta de cómo producir una mejor memcpy .NET.

Luego volví y produje muchas sobrecargas para alimentar fácilmente casi todos los tipos nativos (excepto el decimal †) a un código hash.

Es rápido, por lo que Bob Jenkins merece la mayor parte del crédito porque su código original que porté es aún más rápido, especialmente en máquinas de 64 bits que el algoritmo está optimizado para ‡.

El código completo se puede ver en https://bitbucket.org/JonHanna/spookilysharp/src, pero considere que el código anterior es una versión simplificada del mismo.

Sin embargo, como ya está escrito, uno puede usarlo más fácilmente:

 public override int GetHashCode() { var hash = new SpookyHash(); hash.Update(field1); hash.Update(field2); hash.Update(field3); return hash.Final().GetHashCode(); } 

También toma los valores iniciales, por lo que si necesita tratar con datos que no son de confianza y quiere protegerse contra los ataques Hash DoS, puede establecer una semilla en función del tiempo de actividad o similar, y los atacantes pueden hacer que los resultados sean impredecibles.

 private static long hashSeed0 = Environment.TickCount; private static long hashSeed1 = DateTime.Now.Ticks; public override int GetHashCode() { //produce different hashes ever time this application is restarted //but remain consistent in each run, so attackers have a harder time //DoSing the hash tables. var hash = new SpookyHash(hashSeed0, hashSeed1); hash.Update(field1); hash.Update(field2); hash.Update(field3); return hash.Final().GetHashCode(); } 

* Una gran sorpresa en esto es que se ingresa manualmente un método de rotación que devuelve (x << n) | (x >> -n) (x << n) | (x >> -n) cosas mejoradas. Hubiera estado seguro de que la trepidación habría indicado eso para mí, pero los perfiles mostraron lo contrario.

decimal no es nativo desde la perspectiva de .NET aunque proviene de C #. El problema es que su propio GetHashCode() trata la precisión como significativa mientras que su propio Equals() no lo hace. Ambas son elecciones válidas, pero no mixtas. Al implementar su propia versión, debe elegir hacer una, o la otra, pero no sé cuál de ellas le gustaría.

‡ Por comparación. Si se utiliza en una cadena, SpookyHash en 64 bits es considerablemente más rápido que string.GetHashCode() en 32 bits, que es ligeramente más rápido que string.GetHashCode() en 64 bits, que es considerablemente más rápido que SpookyHash en 32 bits, aunque sigue siendo rápido lo suficiente como para ser una elección razonable.

Este es bueno:

 ///  /// Helper class for generating hash codes suitable /// for use in hashing algorithms and data structures like a hash table. ///  public static class HashCodeHelper { private static int GetHashCodeInternal(int key1, int key2) { unchecked { var num = 0x7e53a269; num = (-1521134295 * num) + key1; num += (num << 10); num ^= (num >> 6); num = ((-1521134295 * num) + key2); num += (num << 10); num ^= (num >> 6); return num; } } ///  /// Returns a hash code for the specified objects ///  /// An array of objects used for generating the /// hash code. ///  /// A hash code, suitable for use in hashing algorithms and data /// structures like a hash table. ///  public static int GetHashCode(params object[] arr) { int hash = 0; foreach (var item in arr) hash = GetHashCodeInternal(hash, item.GetHashCode()); return hash; } ///  /// Returns a hash code for the specified objects ///  /// The first object. /// The second object. /// The third object. /// The fourth object. ///  /// A hash code, suitable for use in hashing algorithms and /// data structures like a hash table. ///  public static int GetHashCode(T1 obj1, T2 obj2, T3 obj3, T4 obj4) { return GetHashCode(obj1, GetHashCode(obj2, obj3, obj4)); } ///  /// Returns a hash code for the specified objects ///  /// The first object. /// The second object. /// The third object. ///  /// A hash code, suitable for use in hashing algorithms and data /// structures like a hash table. ///  public static int GetHashCode(T1 obj1, T2 obj2, T3 obj3) { return GetHashCode(obj1, GetHashCode(obj2, obj3)); } ///  /// Returns a hash code for the specified objects ///  /// The first object. /// The second object. ///  /// A hash code, suitable for use in hashing algorithms and data /// structures like a hash table. ///  public static int GetHashCode(T1 obj1, T2 obj2) { return GetHashCodeInternal(obj1.GetHashCode(), obj2.GetHashCode()); } } 

Y aquí está cómo usarlo:

 private struct Key { private Type _type; private string _field; public Type Type { get { return _type; } } public string Field { get { return _field; } } public Key(Type type, string field) { _type = type; _field = field; } public override int GetHashCode() { return HashCodeHelper.GetHashCode(_field, _type); } public override bool Equals(object obj) { if (!(obj is Key)) return false; var tf = (Key)obj; return tf._field.Equals(_field) && tf._type.Equals(_type); } } 

Aquí está mi enfoque simplista. Estoy usando el patrón de generador clásico para esto. Es tipo seguro (sin boxeo / unboxing) y también compatible con .NET 2.0 (sin métodos de extensión, etc.).

Se usa así:

 public override int GetHashCode() { HashBuilder b = new HashBuilder(); b.AddItems(this.member1, this.member2, this.member3); return b.Result; } 

Y aquí está la clase de constructor acutal:

 internal class HashBuilder { private const int Prime1 = 17; private const int Prime2 = 23; private int result = Prime1; public HashBuilder() { } public HashBuilder(int startHash) { this.result = startHash; } public int Result { get { return this.result; } } public void AddItem(T item) { unchecked { this.result = this.result * Prime2 + item.GetHashCode(); } } public void AddItems(T1 item1, T2 item2) { this.AddItem(item1); this.AddItem(item2); } public void AddItems(T1 item1, T2 item2, T3 item3) { this.AddItem(item1); this.AddItem(item2); this.AddItem(item3); } public void AddItems(T1 item1, T2 item2, T3 item3, T4 item4) { this.AddItem(item1); this.AddItem(item2); this.AddItem(item3); this.AddItem(item4); } public void AddItems(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5) { this.AddItem(item1); this.AddItem(item2); this.AddItem(item3); this.AddItem(item4); this.AddItem(item5); } public void AddItems(params T[] items) { foreach (T item in items) { this.AddItem(item); } } } 

Here is another fluent implementation of the algorithm posted above by Jon Skeet , but which includes no allocations or boxing operations:

 public static class Hash { public const int Base = 17; public static int HashObject(this int hash, object obj) { unchecked { return hash * 23 + (obj == null ? 0 : obj.GetHashCode()); } } public static int HashValue(this int hash, T value) where T : struct { unchecked { return hash * 23 + value.GetHashCode(); } } } 

Uso:

 public class MyType { public string Name { get; set; } public string Description { get; set; } public int Value { get; set; } public IEnumerable Children { get; set; } public override int GetHashCode() { return Hash.Base .HashObject(this.Name) .HashObject(this.Description) .HashValue(this.Value) .HashObject(this.Children); } } 

The compiler will ensure HashValue is not called with a class due to the generic type constraint. But there is no compiler support for HashObject since adding a generic argument also adds a boxing operation.

As of https://github.com/dotnet/coreclr/pull/14863 , there is a new way to generate hash codes that is super simple! Just write

 public override int GetHashCode() => HashCode.Combine(field1, field2, field3); 

This will generate a quality hash code without you having to worry about the implementation details.

ReSharper users can generate GetHashCode, Equals, and others with ReSharper -> Edit -> Generate Code -> Equality Members .

 // ReSharper's GetHashCode looks like this public override int GetHashCode() { unchecked { int hashCode = Id; hashCode = (hashCode * 397) ^ IntMember; hashCode = (hashCode * 397) ^ OtherIntMember; hashCode = (hashCode * 397) ^ (RefMember != null ? RefMember.GetHashCode() : 0); // ... return hashCode; } } 

Most of my work is done with database connectivity which means that my classes all have a unique identifier from the database. I always use the ID from the database to generate the hashcode.

 // Unique ID from database private int _id; ... { return _id.GetHashCode(); } 

Pretty much similar to nightcoder’s solution except it’s easier to raise primes if you want to.

PS: This is one of those times where you puke a little in your mouth, knowing that this could be refactored into one method with 9 default’s but it would be slower, so you just close your eyes and try to forget about it.

 ///  /// Try not to look at the source code. It works. Just rely on it. ///  public static class HashHelper { private const int PrimeOne = 17; private const int PrimeTwo = 23; public static int GetHashCode(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10) { unchecked { int hash = PrimeOne; hash = hash * PrimeTwo + arg1.GetHashCode(); hash = hash * PrimeTwo + arg2.GetHashCode(); hash = hash * PrimeTwo + arg3.GetHashCode(); hash = hash * PrimeTwo + arg4.GetHashCode(); hash = hash * PrimeTwo + arg5.GetHashCode(); hash = hash * PrimeTwo + arg6.GetHashCode(); hash = hash * PrimeTwo + arg7.GetHashCode(); hash = hash * PrimeTwo + arg8.GetHashCode(); hash = hash * PrimeTwo + arg9.GetHashCode(); hash = hash * PrimeTwo + arg10.GetHashCode(); return hash; } } public static int GetHashCode(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9) { unchecked { int hash = PrimeOne; hash = hash * PrimeTwo + arg1.GetHashCode(); hash = hash * PrimeTwo + arg2.GetHashCode(); hash = hash * PrimeTwo + arg3.GetHashCode(); hash = hash * PrimeTwo + arg4.GetHashCode(); hash = hash * PrimeTwo + arg5.GetHashCode(); hash = hash * PrimeTwo + arg6.GetHashCode(); hash = hash * PrimeTwo + arg7.GetHashCode(); hash = hash * PrimeTwo + arg8.GetHashCode(); hash = hash * PrimeTwo + arg9.GetHashCode(); return hash; } } public static int GetHashCode(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) { unchecked { int hash = PrimeOne; hash = hash * PrimeTwo + arg1.GetHashCode(); hash = hash * PrimeTwo + arg2.GetHashCode(); hash = hash * PrimeTwo + arg3.GetHashCode(); hash = hash * PrimeTwo + arg4.GetHashCode(); hash = hash * PrimeTwo + arg5.GetHashCode(); hash = hash * PrimeTwo + arg6.GetHashCode(); hash = hash * PrimeTwo + arg7.GetHashCode(); hash = hash * PrimeTwo + arg8.GetHashCode(); return hash; } } public static int GetHashCode(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) { unchecked { int hash = PrimeOne; hash = hash * PrimeTwo + arg1.GetHashCode(); hash = hash * PrimeTwo + arg2.GetHashCode(); hash = hash * PrimeTwo + arg3.GetHashCode(); hash = hash * PrimeTwo + arg4.GetHashCode(); hash = hash * PrimeTwo + arg5.GetHashCode(); hash = hash * PrimeTwo + arg6.GetHashCode(); hash = hash * PrimeTwo + arg7.GetHashCode(); return hash; } } public static int GetHashCode(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) { unchecked { int hash = PrimeOne; hash = hash * PrimeTwo + arg1.GetHashCode(); hash = hash * PrimeTwo + arg2.GetHashCode(); hash = hash * PrimeTwo + arg3.GetHashCode(); hash = hash * PrimeTwo + arg4.GetHashCode(); hash = hash * PrimeTwo + arg5.GetHashCode(); hash = hash * PrimeTwo + arg6.GetHashCode(); return hash; } } public static int GetHashCode(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) { unchecked { int hash = PrimeOne; hash = hash * PrimeTwo + arg1.GetHashCode(); hash = hash * PrimeTwo + arg2.GetHashCode(); hash = hash * PrimeTwo + arg3.GetHashCode(); hash = hash * PrimeTwo + arg4.GetHashCode(); hash = hash * PrimeTwo + arg5.GetHashCode(); return hash; } } public static int GetHashCode(T1 arg1, T2 arg2, T3 arg3, T4 arg4) { unchecked { int hash = PrimeOne; hash = hash * PrimeTwo + arg1.GetHashCode(); hash = hash * PrimeTwo + arg2.GetHashCode(); hash = hash * PrimeTwo + arg3.GetHashCode(); hash = hash * PrimeTwo + arg4.GetHashCode(); return hash; } } public static int GetHashCode(T1 arg1, T2 arg2, T3 arg3) { unchecked { int hash = PrimeOne; hash = hash * PrimeTwo + arg1.GetHashCode(); hash = hash * PrimeTwo + arg2.GetHashCode(); hash = hash * PrimeTwo + arg3.GetHashCode(); return hash; } } public static int GetHashCode(T1 arg1, T2 arg2) { unchecked { int hash = PrimeOne; hash = hash * PrimeTwo + arg1.GetHashCode(); hash = hash * PrimeTwo + arg2.GetHashCode(); return hash; } } } 

I ran into an issue with floats and decimals using the implementation selected as the answer above.

This test fails (floats; hash is the same even though I switched 2 values to be negative):

  var obj1 = new { A = 100m, B = 100m, C = 100m, D = 100m}; var obj2 = new { A = 100m, B = 100m, C = -100m, D = -100m}; var hash1 = ComputeHash(obj1.A, obj1.B, obj1.C, obj1.D); var hash2 = ComputeHash(obj2.A, obj2.B, obj2.C, obj2.D); Assert.IsFalse(hash1 == hash2, string.Format("Hashcode values should be different hash1:{0} hash2:{1}",hash1,hash2)); 

But this test passes (with ints):

  var obj1 = new { A = 100m, B = 100m, C = 100, D = 100}; var obj2 = new { A = 100m, B = 100m, C = -100, D = -100}; var hash1 = ComputeHash(obj1.A, obj1.B, obj1.C, obj1.D); var hash2 = ComputeHash(obj2.A, obj2.B, obj2.C, obj2.D); Assert.IsFalse(hash1 == hash2, string.Format("Hashcode values should be different hash1:{0} hash2:{1}",hash1,hash2)); 

I changed my implementation to not use GetHashCode for the primitive types and it seems to work better

  private static int InternalComputeHash(params object[] obj) { unchecked { var result = (int)SEED_VALUE_PRIME; for (uint i = 0; i < obj.Length; i++) { var currval = result; var nextval = DetermineNextValue(obj[i]); result = (result * MULTIPLIER_VALUE_PRIME) + nextval; } return result; } } private static int DetermineNextValue(object value) { unchecked { int hashCode; if (value is short || value is int || value is byte || value is sbyte || value is uint || value is ushort || value is ulong || value is long || value is float || value is double || value is decimal) { return Convert.ToInt32(value); } else { return value != null ? value.GetHashCode() : 0; } } } 

If we have no more than 8 properties (hopefully), here is another alternative.

ValueTuple is a struct and appears to have a solid GetHashCode implementation.

That means we could simply do this:

 // Yay, no allocations and no custom implementations! public override int GetHashCode() => (this.PropA, this.PropB).GetHashCode(); 

Let’s take a look at .NET Core’s current implementation for ValueTuple ‘s GetHashCode .

This is from ValueTuple :

  internal static int CombineHashCodes(int h1, int h2) { return HashHelpers.Combine(HashHelpers.Combine(HashHelpers.RandomSeed, h1), h2); } internal static int CombineHashCodes(int h1, int h2, int h3) { return HashHelpers.Combine(CombineHashCodes(h1, h2), h3); } 

And this is from HashHelper :

  public static readonly int RandomSeed = Guid.NewGuid().GetHashCode(); public static int Combine(int h1, int h2) { unchecked { // RyuJIT optimizes this to use the ROL instruction // Related GitHub pull request: dotnet/coreclr#1830 uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27); return ((int)rol5 + h1) ^ h2; } } 

In English:

  • Left rotate (circular shift) h1 by 5 positions.
  • Add the result and h1 together.
  • XOR the result with h2.
  • Start by performing the above operation on { static random seed, h1 }.
  • For each further item, perform the operation on the previous result and the next item (eg h2).

It would be nice to know more about the properties of this ROL-5 hash code algorithm.

Regrettably, deferring to ValueTuple for our own GetHashCode may not be as fast as we would like and expect. This comment in a related discussion illustrates that directly calling HashHelpers.Combine is more performant. On the flip side, that one is internal, so we’d have to copy the code, sacrificing much of what we had gained here. Also, we’d be responsible for remembering to first Combine with the random seed. I don’t know what the consequences are if we skip that step.

Microsoft lead for several way of hashing…

 //for classes that contain a single int value return this.value; //for classes that contain multiple int value return x ^ y; //for classes that contain single number bigger than int return ((int)value ^ (int)(value >> 32)); //for classes that contain class instance fields which inherit from object return obj1.GetHashCode(); //for classes that contain multiple class instance fields which inherit from object return obj1.GetHashCode() ^ obj2.GetHashCode() ^ obj3.GetHashCode(); 

I can guess that for multiple big int you can use this:

 int a=((int)value1 ^ (int)(value1 >> 32)); int b=((int)value2 ^ (int)(value2 >> 32)); int c=((int)value3 ^ (int)(value3 >> 32)); return a ^ b ^ c; 

And same for multi-type: all converted first to int using GetHashCode() then the int values will be xor’ed and the result is your hash.

For those who use hash as ID (I mean an unique value), hash is naturally limited to a number of digits, I think it was 5 bytes for hashing algorithm, at least MD5.

You may turn multiple values to a hashed value and some of them be same, so don’t use it as an identifier. (maybe some day I am going to use your component)