Cuál es el mejor algoritmo para un sistema anulado.Objeto.¿GetHashCode?


En.NET System.Object.GetHashCode el método se usa en muchos lugares, en todas las bibliotecas de clases base de. NET. Especialmente al encontrar artículos en una colección rápida o para determinar la igualdad. ¿Hay un algoritmo estándar / mejores prácticas sobre cómo implementar la anulación GetHashCode para mis clases personalizadas para que no degrade el rendimiento?

Author: iYoung, 2008-11-04

17 answers

Suelo ir con algo como la implementación dada en Josh Bloch fabuloso Java efectivo . Es rápido y crea un hachís bastante bueno que es poco probable que cause colisiones. Elija dos números primos diferentes, por ejemplo, 17 y 23, y haga:

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 indica en los comentarios, es posible que sea mejor elegir un primo grande para multiplicar por. 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 en algoritmos menos similares donde los números no primos se utilizan a menudo. En el no-del todo-FNV ejemplo posterior, por ejemplo, he utilizado números que aparentemente funcionan bien, pero el valor inicial no es un primo. (La constante de multiplicación es primo sin embargo. No se que tan importante es eso.)

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

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

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

Esta página da 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 hacer bien. La alternativa FNV es similarmente simple, pero usa diferentes constantes y XOR en lugar de ADD como una operación combinada. Se ve algo como el código de abajo, pero el algoritmo FNV normal opera en bytes individuales, por lo que esto sería require modifying para realizar una iteración por byte, en lugar de por valor hash de 32 bits. FNV también está diseñado para longitudes variables de datos, 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 ejemplo 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 su igualdad sensible (y por lo tanto hashcode-sensitive) estado de cambio 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 tipos de referencia mutables, debe anular GetHashCode solo si:

  • Puede calcular el código hash a partir de 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.
 1382
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-16 07:20:13

Tipo anónimo

Microsoft ya proporciona un buen generador de hashCode genérico: Simplemente copie sus valores de propiedad/campo a un tipo anónimo y hash:

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

Esto funcionará para cualquier número de propiedades. No usa boxeo. Solo utiliza el algoritmo ya implementado en el framework 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 teclado y más es importante destacar que se ejecuta puramente en la pila (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 compilador. Sería interesante comparar estas opciones, pero la opción tupla debería ser superior.)

 308
Author: Rick Love,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-16 14:13:51

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

public static class HashHelper
{
    public static int GetHashCode<T1, T2>(T1 arg1, T2 arg2)
    {
         unchecked
         {
             return 31 * arg1.GetHashCode() + arg2.GetHashCode();
         }
    }

    public static int GetHashCode<T1, T2, T3>(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, T2, T3, T4>(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>(T[] list)
    {
        unchecked
        {
            int hash = 0;
            foreach (var item in list)
            {
                hash = 31 * hash + item.GetHashCode();
            }
            return hash;
        }
    }

    public static int GetHashCode<T>(IEnumerable<T> list)
    {
        unchecked
        {
            int hash = 0;
            foreach (var item in list)
            {
                hash = 31 * hash + item.GetHashCode();
            }
            return hash;
        }
    }

    /// <summary>
    /// 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.
    /// </summary>
    public static int GetHashCodeForOrderNoMatterCollection<T>(
        IEnumerable<T> list)
    {
        unchecked
        {
            int hash = 0;
            int count = 0;
            foreach (var item in list)
            {
                hash += item.GetHashCode();
                count++;
            }
            return 31 * hash + count.GetHashCode();
        }
    }

    /// <summary>
    /// Alternative way to get a hashcode is to use a fluent 
    /// interface like this:<br />
    /// return 0.CombineHashCode(field1).CombineHashCode(field2).
    ///     CombineHashCode(field3);
    /// </summary>
    public static int CombineHashCode<T>(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 de la siguiente manera:

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

O así:

public override int GetHashCode()
{
    return 0.CombineHashCode(Manufacturer)
        .CombineHashCode(PartN)
        .CombineHashCode(Quantity);
}
 94
Author: nightcoder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-03-25 15:29:38

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

/// <summary> 
/// 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
/// </summary>
/// <param name="input">array of objects, parameters combination that you need
/// to get a unique hash code for them</param>
/// <returns>Hash code</returns>
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 puede usarlo como:

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

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

 57
Author: Wahid Shalaly,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-22 12:00:19

Aquí está mi clase helper usando La implementación de Jon Skeet.

public static class HashCode
{
    public const int Start = 17;

    public static int Hash<T>(this int hash, T obj)
    {
        var h = EqualityComparer<T>.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>(T obj)
    {
        var h = EqualityComparer<T>.Default.GetHashCode(obj);
        return unchecked(new HashCode((_value * 31) + h));
    }

    public override int GetHashCode() => _value;
}

Sigue siendo genérico, aún evita cualquier asignación de montón 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.

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

Editar (mayo 2018):

EqualityComparer<T>.Default getter es ahora un JIT intrínseco-el pull request es mencionado por Stephen Toub en esta entrada de blog.

 50
Author: Şafak Gür,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-06 14:18:58

En la mayoría de los casos donde Equals() compara varios campos, realmente no importa si tu GetHash() tiene hashes en un campo o en muchos. Solo tienes que asegurarte de que calcular el hash sea realmente barato (Sin asignaciones, por favor) y rápido (Sin cálculos pesados y ciertamente sin conexiones de base de datos) y proporcione una buena distribución.

El levantamiento pesado debe ser parte del método Equals (); el hash debe ser una operación muy barata para permitir llamar a Equals () en as pocos elementos como sea posible.

Y un consejo final: No confíes en que GetHashCode() sea estable sobre múltiples ejecuciones de aplicación. Muchos tipos. 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 en memoria.

 26
Author: Bert Huijben,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-23 11:55:44

Hasta hace poco mi respuesta habría sido muy cercana a la de Jon Skeet aquí. Sin embargo, recientemente comencé un proyecto que usaba tablas hash de potencia de dos, es decir, 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 también hay algunas ventajas en los tamaños de potencia de dos.

Y fue una mierda. Así que después de un poco de experimentación e investigación empecé a volver a hashear mis hashes con el 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 entonces mi tabla de hash de poder de dos ya no apesta.

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

Volver a mezclar un hashcode no puede mejorar un gran hashcode, porque el único efecto posible es que introduzcamos algunas colisiones más.

Volver a mezclar un código hash no puede mejorar un código hash terrible, porque el único el efecto posible es que cambiamos, por ejemplo, un gran número 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 lo hizo al menos bastante bien en evitar colisiones absolutas en todo su rango (232 posibles valores) pero mal evitando colisiones cuando modulo'd hacia abajo para su uso real en una tabla hash. Mientras que el módulo más simple de una tabla de poder de dos hizo esto más evidente, también estaba teniendo un efecto negativo con las tablas de números primos más comunes, que simplemente no eran tan obvias (el trabajo adicional en el rehashing superaría el beneficio, pero el beneficio seguiría ahí).

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

Y bueno, era inquietante cuánto las implementaciones string.GetHashCode() en . NET (o study aquí) podrían mejorarse de esta manera (en el orden de pruebas que se ejecutan alrededor de 20-30 veces más rápido debido a menos colisiones) y más preocupante la cantidad de mis propios códigos hash podrían mejorarse (mucho más que eso).

Todas las implementaciones de GetHashCode () que había codificado en el pasado, y de hecho utilizado como base de respuestas en este sitio, eran mucho peores de lo que había pasado. La mayor parte del tiempo era "lo suficientemente bueno" para muchos de los usos, pero quería algo mejor.

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

Al final me decidí por portar SpookyHash a .NET. De hecho, el código anterior es una versión de ruta rápida de usar 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ápido para recordar. Mi puerto de la misma es aún menos porque yo mano-inlineado mucho de ella para una mejor velocidad*. Pero para eso es la reutilización de código.

Entonces puse que proyecto a un lado, porque así como el proyecto original había producido la cuestión de cómo producir un mejor código hash, por lo que el proyecto produjo la cuestión de cómo producir un mejor.NET memcpy.

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

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

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, puesto que ahora ya está escrito, uno puede hacer uso de él más fácilmente:{[13]]}

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

También toma valores de semilla, por lo que si necesita lidiar con entradas no confiables y desea protegerse contra ataques Hash DoS, puede establecer una semilla basada en el tiempo de actividad o similar, y hacer que los resultados impredecibles por los atacantes:

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 la inserción manual de un método de rotación que devolvió (x << n) | (x >> -n) mejoró las cosas. Yo habría estado seguro de que el jitter habría inlineado eso para mí, pero el perfil mostró lo contrario.

decimal no es nativo desde la perspectiva de.NET aunque es desde el C#. El problema con él es que su propio GetHashCode() trata la precisión como significativa, mientras que su propio Equals() no lo hace. Ambas son opciones válidas, pero no mezclados así. Al implementar su propia versión, debe elegir hacer una o la otra, pero no puedo saber cuál querría.

‡a modo de comparación. Si se usa en una cadena, el 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 todavía lo suficientemente rápido como para ser una opción razonable.

 19
Author: Jon Hanna,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-04 21:11:03

Esta es una buena:

/// <summary>
/// Helper class for generating hash codes suitable 
/// for use in hashing algorithms and data structures like a hash table. 
/// </summary>
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;
        }
    }

    /// <summary>
    /// Returns a hash code for the specified objects
    /// </summary>
    /// <param name="arr">An array of objects used for generating the 
    /// hash code.</param>
    /// <returns>
    /// A hash code, suitable for use in hashing algorithms and data 
    /// structures like a hash table. 
    /// </returns>
    public static int GetHashCode(params object[] arr)
    {
        int hash = 0;
        foreach (var item in arr)
            hash = GetHashCodeInternal(hash, item.GetHashCode());
        return hash;
    }

    /// <summary>
    /// Returns a hash code for the specified objects
    /// </summary>
    /// <param name="obj1">The first object.</param>
    /// <param name="obj2">The second object.</param>
    /// <param name="obj3">The third object.</param>
    /// <param name="obj4">The fourth object.</param>
    /// <returns>
    /// A hash code, suitable for use in hashing algorithms and
    /// data structures like a hash table.
    /// </returns>
    public static int GetHashCode<T1, T2, T3, T4>(T1 obj1, T2 obj2, T3 obj3,
        T4 obj4)
    {
        return GetHashCode(obj1, GetHashCode(obj2, obj3, obj4));
    }

    /// <summary>
    /// Returns a hash code for the specified objects
    /// </summary>
    /// <param name="obj1">The first object.</param>
    /// <param name="obj2">The second object.</param>
    /// <param name="obj3">The third object.</param>
    /// <returns>
    /// A hash code, suitable for use in hashing algorithms and data 
    /// structures like a hash table. 
    /// </returns>
    public static int GetHashCode<T1, T2, T3>(T1 obj1, T2 obj2, T3 obj3)
    {
        return GetHashCode(obj1, GetHashCode(obj2, obj3));
    }

    /// <summary>
    /// Returns a hash code for the specified objects
    /// </summary>
    /// <param name="obj1">The first object.</param>
    /// <param name="obj2">The second object.</param>
    /// <returns>
    /// A hash code, suitable for use in hashing algorithms and data 
    /// structures like a hash table. 
    /// </returns>
    public static int GetHashCode<T1, T2>(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);
    }
}
 12
Author: Magnus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-12-02 14:20:42

Aquí está mi enfoque simplista. Estoy usando el patrón de constructor clásico para esto. Es typesafe (sin boxeo / unboxing) y también compatbile 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 acutal builder:

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>(T item)
    {
        unchecked
        {
            this.result = this.result * Prime2 + item.GetHashCode();
        }
    }

    public void AddItems<T1, T2>(T1 item1, T2 item2)
    {
        this.AddItem(item1);
        this.AddItem(item2);
    }

    public void AddItems<T1, T2, T3>(T1 item1, T2 item2, T3 item3)
    {
        this.AddItem(item1);
        this.AddItem(item2);
        this.AddItem(item3);
    }

    public void AddItems<T1, T2, T3, T4>(T1 item1, T2 item2, T3 item3, 
        T4 item4)
    {
        this.AddItem(item1);
        this.AddItem(item2);
        this.AddItem(item3);
        this.AddItem(item4);
    }

    public void AddItems<T1, T2, T3, T4, T5>(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<T>(params T[] items)
    {
        foreach (T item in items)
        {
            this.AddItem(item);
        }
    }
}
 8
Author: bitbonk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-15 06:18:32

Aquí hay otra implementación fluida de el algoritmo publicado anteriormente por Jon Skeet, pero que no incluye asignaciones u operaciones de boxeo:

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<T>(this int hash, T value)
        where T : struct
    {
        unchecked { return hash * 23 + value.GetHashCode(); }
    }
}

Uso:

public class MyType<T>
{
    public string Name { get; set; }

    public string Description { get; set; }

    public int Value { get; set; }

    public IEnumerable<T> Children { get; set; }

    public override int GetHashCode()
    {
        return Hash.Base
            .HashObject(this.Name)
            .HashObject(this.Description)
            .HashValue(this.Value)
            .HashObject(this.Children);
    }
}

El compilador se asegurará de que HashValue no se llame con una clase debido a la restricción de tipo genérico. Pero no hay soporte de compilador para HashObject ya que agregar un argumento genérico también agrega una operación de boxeo.

 8
Author: Scott Wegner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 10:31:37

A partir de https://github.com/dotnet/coreclr/pull/14863, hay una nueva forma de generar códigos hash que es súper simple! Solo escribe

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

Esto generará un código hash de calidad sin que tenga que preocuparse por los detalles de la implementación.

 6
Author: James Ko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-23 15:06:05

Los usuarios de ReSharper pueden generar GetHashCode, Equals y otros con 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;
    }
}
 4
Author: Charles Burns,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-05 14:19:15

La mayor parte de mi trabajo se realiza con conectividad de base de datos, lo que significa que todas mis clases tienen un identificador único de la base de datos. Siempre uso el ID de la base de datos para generar el hashcode.

// Unique ID from database
private int _id;

...    
{
  return _id.GetHashCode();
}
 3
Author: Mark G,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-11-05 05:03:24

Bastante similar a la solución de nightcoder, excepto que es más fácil elevar números primos si lo desea.

PD: Esta es una de esas veces donde vomitas un poco en la boca, sabiendo que esto podría ser refactorizado en un método con 9 por defecto, pero sería más lento, por lo que solo cierra los ojos y trata de olvidarlo.

/// <summary>
/// Try not to look at the source code. It works. Just rely on it.
/// </summary>
public static class HashHelper
{
    private const int PrimeOne = 17;
    private const int PrimeTwo = 23;

    public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(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, T2, T3, T4, T5, T6, T7, T8, T9>(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, T2, T3, T4, T5, T6, T7, T8>(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, T2, T3, T4, T5, T6, T7>(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, T2, T3, T4, T5, T6>(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, T2, T3, T4, T5>(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, T2, T3, T4>(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, T2, T3>(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, T2>(T1 arg1, T2 arg2)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();

            return hash;
        }
    }
}
 3
Author: Dbl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-21 17:49:34

Me encontré con un problema con flotadores y decimales usando la implementación seleccionada como la respuesta anterior.

Esta prueba falla (flota; hash es el mismo aunque cambié 2 valores para ser negativos):

        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));

Pero esta prueba pasa (con 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));

Cambié mi implementación para no usar GetHashCode para los tipos primitivos y parece funcionar mejor

    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;
                }
        }
    }
 1
Author: HokieMike,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-28 16:44:25

Si no tenemos más de 8 propiedades (esperemos), aquí hay otra alternativa.

ValueTuple es una estructura y parece tener una implementación sólida GetHashCode.

Eso significa que simplemente podríamos hacer esto: {[14]]}

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

Echemos un vistazo a la implementación actual de.NET Core para ValueTuple's GetHashCode.

Esto es de 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);
    }

Y esto es de 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;
        }
    }

En inglés:

  • Girar a la izquierda (desplazamiento circular) h1 por 5 posiciones.
  • Suma el resultado y h1 juntos.
  • XOR el resultado con h2.
  • Comience por realizar la operación anterior en { static random seed, h1}.
  • Para cada elemento adicional, realice la operación sobre el resultado anterior y el elemento siguiente (por ejemplo, h2).

Sería bueno saber más acerca de las propiedades de este algoritmo de código hash ROL-5.

Lamentablemente, postergar a ValueTuple para nuestro propio GetHashCode puede no ser tan rápido como nos gustaría y esperar. Este comentario en una discusión relacionada ilustra que llamar directamente a HashHelpers.Combine es más performante. Por otro lado, ese es interno, así que tendríamos que copiar el código, sacrificando mucho de lo que habíamos ganado aquí. Además, seríamos responsables de recordar primero Combine con la semilla aleatoria. No se cuales son las consecuencias si saltamos ese paso.

 1
Author: Timo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-15 12:00:46

Microsoft lead para varias formas de 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(); 

Puedo adivinar que para múltiples grandes int puedes usar esto:

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;

Y lo mismo para multi-type: todos convertidos primero a int usando GetHashCode() entonces los valores int serán xor'ed y el resultado será tu hash.

Para aquellos que usan hash como ID (me refiero a un valor único), hash está naturalmente limitado a un número de dígitos, creo que fue de 5 bytes para el algoritmo de hash, al menos MD5.

Puede convertir varios valores en un valor hash y algunos de ellos son iguales, así que no lo uses como identificador. (tal vez algún día voy a utilizar su componente)

 0
Author: deadManN,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-06 11:48:24