.net - practices - Quel est le meilleur algorithme pour un System.Object.GetHashCode surchargé?




microsoft gethashcode (11)

En .NET System.Object.GetHashCode méthode est utilisée dans beaucoup d'endroits, à travers les bibliothèques de classes de base .NET. Surtout lorsque vous recherchez rapidement des articles dans une collection ou pour déterminer l'égalité. Existe-t-il un algorithme / une bonne pratique standard sur la façon d'implémenter le remplacement GetHashCode pour mes classes personnalisées afin de ne pas dégrader les performances?


C'est une bonne:

/// <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());
    }
}

Et voici comment l'utiliser:

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

Dans la plupart des cas où Equals () compare plusieurs champs, cela n'a pas vraiment d'importance si votre GetHash () hash sur un champ ou sur plusieurs. Vous devez juste vous assurer que le calcul du hachage est vraiment bon marché ( aucune allocation , s'il vous plaît) et rapide ( pas de calculs lourds et certainement pas de connexions de base de données) et fournit une bonne distribution.

Le levage lourd devrait faire partie de la méthode Equals (); le hachage devrait être une opération très bon marché pour permettre d'appeler Equals () sur aussi peu d'éléments que possible.

Et un dernier conseil: Ne comptez pas sur GetHashCode () étant stable sur plusieurs exécutions d'application . De nombreux types .Net ne garantissent pas que leurs codes de hachage restent les mêmes après un redémarrage. Par conséquent, vous ne devez utiliser la valeur de GetHashCode () que pour les structures de données en mémoire.


Je vais généralement avec quelque chose comme l'implémentation donnée dans le fabuleux Java efficace de Josh Bloch. C'est rapide et crée un assez bon hasch qui est peu susceptible de causer des collisions. Choisissez deux nombres premiers différents, par exemple 17 et 23, et faites:

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

Comme indiqué dans les commentaires, vous trouverez peut-être préférable de choisir un grand nombre à multiplier par plutôt. Apparemment, 486187739 est bon ... et bien que la plupart des exemples que j'ai vus avec de petits nombres aient tendance à utiliser des nombres premiers, il existe au moins des algorithmes similaires où les nombres non premiers sont souvent utilisés. Dans l'exemple non-tout à fait FNV plus tard, par exemple, j'ai utilisé des nombres qui fonctionnent apparemment bien - mais la valeur initiale n'est pas un nombre premier. (La constante de multiplication est primordiale, mais je ne sais pas à quel point c'est important.)

C'est mieux que la pratique courante des hashcodes XOR ing pour deux raisons principales. Supposons que nous ayons un type avec deux champs int :

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

D'ailleurs, l'algorithme précédent est celui actuellement utilisé par le compilateur C # pour les types anonymes.

Cette page donne pas mal d'options. Je pense que, dans la plupart des cas, ce qui précède est «assez bon» et il est incroyablement facile de s'en souvenir et de bien faire les choses. L'alternative FNV est similaire simple, mais utilise des constantes différentes et XOR au lieu de ADD comme une opération de combinaison. Il ressemble à peu près au code ci-dessous, mais l'algorithme FNV normal fonctionne sur des octets individuels, ce qui nécessite une modification pour effectuer une itération par octet, plutôt que par valeur de hachage 32 bits. FNV est également conçu pour des longueurs de données variables, alors que nous l'utilisons ici toujours pour le même nombre de valeurs de champs. Les commentaires sur cette réponse suggèrent que le code ici ne fonctionne pas aussi bien (dans l'échantillon testé) que l'approche d'addition ci-dessus.

// 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;
    }
}

Notez qu'une chose à savoir est que, dans l'idéal, vous devriez éviter que votre état sensible à l'égalité (et donc au code hachage) ne change après l'avoir ajouté à une collection qui dépend du code de hachage.

Selon la documentation :

Vous pouvez remplacer GetHashCode pour les types de référence immuables. En général, pour les types de référence modifiables, vous devez remplacer GetHashCode uniquement si:

  • Vous pouvez calculer le code de hachage à partir des champs qui ne sont pas modifiables; ou
  • Vous pouvez vous assurer que le code de hachage d'un objet mutable ne change pas lorsque l'objet est contenu dans une collection qui repose sur son code de hachage.

Jusqu'à récemment, ma réponse aurait été très proche de celle de Jon Skeet. Cependant, j'ai récemment démarré un projet qui utilisait des tables de hachage power-of-two, c'est-à-dire des tables de hachage dont la taille est de 8, 16, 32, etc. Il y a de bonnes raisons de privilégier les tailles de nombres premiers. Certains avantages sont également liés à la puissance de deux tailles.

Et ça a été plutôt nul. Donc, après un peu d'expérimentation et de recherche, j'ai commencé à refaire mes hachages avec ce qui suit:

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

Et puis ma table de hachage power-of-two ne m'a plus rien sucé.

Cela m'a dérangé cependant, parce que ce qui précède ne devrait pas fonctionner. Ou plus précisément, cela ne devrait pas fonctionner à moins que le GetHashCode() soit pauvre d'une manière très particulière.

Re-mélanger un code de hachage ne peut pas améliorer un grand code de hachage, car le seul effet possible est que nous introduisons quelques collisions de plus.

Re-mélanger un code de hachage ne peut pas améliorer un code de hachage terrible, car le seul effet possible est nous changeons par exemple un grand nombre de collisions sur la valeur 53 à un grand nombre de valeur 18,3487,291.

Le remixage d'un code de hachage ne peut qu'améliorer un code de hachage qui a au moins assez bien évité les collisions absolues dans toute son étendue (2 32 valeurs possibles) mais à éviter les collisions lorsqu'il est modulé pour une utilisation réelle dans une table de hachage. Tandis que le modulo plus simple d'une table de deux puissances le rendait plus apparent, il avait aussi un effet négatif sur les tables de nombres premiers les plus courantes, ce qui n'était pas aussi évident (le travail supplémentaire de rehaussement l'emportait sur le bénéfice , mais le bénéfice serait toujours là).

Edit: J'utilisais aussi l'adressage ouvert, ce qui aurait également augmenté la sensibilité à la collision, peut-être plus que le fait qu'il s'agissait d'un power-of-two.

Et bien, cela dérangeait à quel point les string.GetHashCode() dans .NET (ou étudiez here ) pourraient être améliorées de cette façon (dans l'ordre des tests qui tournent environ 20-30 fois plus vite en raison du moins de collisions) et plus troublantes beaucoup de mes propres codes de hachage pourraient être améliorés (beaucoup plus que cela).

Toutes les implémentations GetHashCode () que j'avais codées dans le passé, et qui étaient en fait utilisées comme base de réponses sur ce site, étaient bien pires que je ne l'aurais fait . La plupart du temps, c'était «assez bon» pour la plupart des utilisations, mais je voulais quelque chose de mieux.

J'ai donc mis ce projet de côté (c'était de toute façon un projet favori) et j'ai commencé à chercher comment produire rapidement un bon code de hachage bien distribué dans .NET.

À la fin je me suis installé sur le portage de SpookyHash à .NET. En effet, le code ci-dessus est une version rapide de l'utilisation de SpookyHash pour produire une sortie 32 bits à partir d'une entrée 32 bits.

Maintenant, SpookyHash n'est pas un bon souvenir de morceau de code. Mon portage est encore moins important parce que j'en ai mis beaucoup en main pour une meilleure vitesse *. Mais c'est ce à quoi sert la réutilisation du code.

Ensuite, j'ai mis ce projet de côté, car tout comme le projet original avait produit la question de savoir comment produire un meilleur code de hachage, ce projet a soulevé la question de savoir comment produire un meilleur memoire .NET.

Puis je suis revenu, et j'ai produit beaucoup de surcharges pour facilement nourrir tous les types natifs (sauf la decimal †) dans un code de hachage.

C'est rapide, pour lequel Bob Jenkins mérite le plus de crédit car son code d'origine est plus rapide, surtout sur les machines 64 bits dont l'algorithme est optimisé pour ‡.

Le code complet peut être vu à https://bitbucket.org/JonHanna/spookilysharp/src mais considérons que le code ci-dessus est une version simplifiée de celui-ci.

Cependant, puisqu'il est déjà écrit, on peut s'en servir plus facilement:

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

Il prend aussi des valeurs de départ, donc si vous avez besoin de traiter des entrées non fiables et que vous voulez protéger contre les attaques Hash DoS, vous pouvez définir une graine basée sur uptime ou similaire, et rendre les résultats imprévisibles par les attaquants:

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

* Une grande surprise est que cette méthode de rotation retourne manuellement (x << n) | (x >> -n) (x << n) | (x >> -n) choses améliorées. J'aurais été sûr que la gigue aurait souligné cela pour moi, mais le profilage a montré le contraire.

decimal n'est pas natif de la perspective .NET, bien qu'il provienne du C #. Le problème avec cela est que son propre GetHashCode() traite la précision comme significative alors que son propre Equals() ne le fait pas. Les deux sont des choix valables, mais pas mélangés comme ça. En implémentant votre propre version, vous devez choisir de faire l'un ou l'autre, mais je ne peux pas savoir lequel vous voulez.

‡ A titre de comparaison. Si utilisé sur une chaîne, le SpookyHash sur 64 bits est considérablement plus rapide que string.GetHashCode() sur 32 bits qui est légèrement plus rapide que string.GetHashCode() sur 64 bits, ce qui est considérablement plus rapide que SpookyHash sur 32 bits, mais toujours rapide assez pour être un choix raisonnable.


Voici ma classe d'aide qui utilise l' implémentation 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);
    }
}

Usage:

public override int GetHashCode()
{
    return HashCode.Start
        .Hash(field1)
        .Hash(field2)
        .Hash(field3);
}

Si vous voulez éviter d'écrire une méthode d'extension pour System.Int32:

public struct HashCode
{
    private readonly int hashCode;

    public HashCode(int hashCode)
    {
        this.hashCode = hashCode;
    }

    public static HashCode Start { get; } = new HashCode(17);

    public static implicit operator int(HashCode hashCode)
        => hashCode.GetHashCode();

    public HashCode Hash<T>(T obj)
    {
        var h = EqualityComparer<T>.Default.GetHashCode(obj);
        return unchecked(new HashCode((this.hashCode * 31) + h));
    }

    public override int GetHashCode()
        => this.hashCode;
}

Il est toujours générique, il évite toujours toute allocation de tas et il est utilisé exactement de la même manière:

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

Mise à jour après le commentaire de Martin:

obj != null causé la boxe donc je suis passé au comparateur par défaut.

  • Voir cette réponse concernant les performances du comparateur par défaut.
  • Voir cette question pour une discussion sur les codes de hachage des valeurs nulles.

Voici mon approche simpliste. J'utilise le modèle de constructeur classique pour cela. Il est de typesafe (pas boxing / unboxing) et aussi compatbile avec .NET 2.0 (pas de méthodes d'extension etc.).

Il est utilisé comme ceci:

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

Et voici la classe constructeur Buutal:

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

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

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.


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

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)


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.

/// <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;
        }
    }
}




gethashcode