130 votes

Utilisation du champ d'un objet comme clé générique du dictionnaire

Si je veux utiliser des objets comme clés pour un Dictionnaire, quelles méthodes devrais-je remplacer pour les comparer d'une manière spécifique ?

Supposons que j'ai une classe avec des propriétés :

class Foo {
    public string Name { get; set; }
    public int FooID { get; set; }

    // elided
} 

Et je veux créer un :

Dictionnaire>

Je veux que les objets Foo avec le même FooID soient considérés comme le même groupe. Quelles méthodes devrais-je remplacer dans la classe Foo ?

En résumé : je veux catégoriser les objets Stuff dans des listes, regroupés par des objets Foo. Les objets Stuff auront un FooID pour les lier à leur catégorie.

167voto

Marc Gravell Points 482669

Par défaut, les deux méthodes importantes sont GetHashCode() et Equals(). Il est important que si deux éléments sont égaux (Equals() renvoie true), ils aient le même code de hachage. Par exemple, vous pourriez "return FooID;" comme GetHashCode() si vous le souhaitez comme critère de correspondance. Vous pouvez également implémenter IEquatable, mais cela est facultatif :

class Foo : IEquatable {
    public string Name { get; set;}
    public int FooID {get; set;}

    public override int GetHashCode() {
        return FooID;
    }
    public override bool Equals(object obj) {
        return Equals(obj as Foo);
    }
    public bool Equals(Foo obj) {
        return obj != null && obj.FooID == this.FooID;
    }
}

Enfin, une autre alternative est de fournir un IEqualityComparer pour faire la même chose.

5 votes

+1 et je ne veux pas détourner ce fil de discussion mais j'avais l'impression que GetHashCode() devrait renvoyer FooId.GetHashCode(). Est-ce que ce n'est pas le bon motif?

8 votes

@Ken - eh bien, il suffit simplement de renvoyer un int qui fournit les fonctionnalités nécessaires. Quel que soit le FooID fera aussi bien l'affaire que FooID.GetHashCode(). En tant que détail d'implémentation, Int32.GetHashCode() est "return this;". Pour d'autres types (chaîne etc), alors oui : .GetHashCode() serait très utile.

2 votes

Merci! J'ai choisi l'IEqualityComparer car c'était uniquement pour le Dicarionary que j'avais besoin des méthodes surchargées.

34voto

Guffa Points 308133

Comme vous voulez que le FooID soit l'identifiant du groupe, vous devriez l'utiliser comme clé dans le dictionnaire au lieu de l'objet Foo :

Dictionary>

Si vous utilisiez l'objet Foo comme clé, il vous suffirait d'implémenter les méthodes GetHashCode et Equals pour ne considérer que la propriété FooID. La propriété Name ne serait alors que du poids mort du point de vue du Dictionary, vous utiliseriez donc Foo comme un conteneur pour un int.

Il est donc préférable d'utiliser directement la valeur FooID, et vous n'avez pas besoin d'implémenter quoi que ce soit car le Dictionary prend déjà en charge l'utilisation d'un int comme clé.

Édition :
Si vous souhaitez malgré tout utiliser la classe Foo comme clé, l'interface IEqualityComparer est facile à implémenter :

public class FooEqualityComparer : IEqualityComparer {
   public int GetHashCode(Foo foo) { return foo.FooID.GetHashCode(); }
   public bool Equals(Foo foo1, Foo foo2) { return foo1.FooID == foo2.FooID; }
}

Utilisation :

Dictionary> dict = new Dictionary>(new FooEqualityComparer());

1 votes

Plus correctement, int prend déjà en charge les méthodes/interfaces requises pour être utilisé comme clé. Le dictionnaire n'a aucune connaissance directe de int ou de tout autre type.

0 votes

J'ai pensé à cela, mais pour diverses raisons, il était plus propre et plus pratique d'utiliser les objets comme clés de dictionnaire.

1 votes

Eh bien, il semble seulement que vous utilisez l'objet comme clé, alors que vous n'utilisez vraiment que l'identifiant comme clé.

9voto

froh42 Points 2759

Pour Foo, vous devrez remplacer object.GetHashCode() et object.Equals()

Le dictionnaire appellera GetHashCode() pour calculer un compartiment de hachage pour chaque valeur et Equals pour comparer si deux Foo sont identiques.

Assurez-vous de calculer de bons codes de hachage (évitez que de nombreux objets Foo égaux aient le même code de hachage), mais assurez-vous que deux Foo égaux ont le même code de hachage. Vous voudrez peut-être commencer par la méthode Equals et ensuite (dans GetHashCode()) xor le code de hachage de chaque membre que vous comparez dans Equals.

public class Foo { 
     public string A;
     public string B;

     override bool Equals(object other) {
          var otherFoo = other as Foo;
          if (otherFoo == null)
             return false;
          return A==otherFoo.A && B ==otherFoo.B;
     }

     override int GetHashCode() {
          return 17 * A.GetHashCode() + B.GetHashCode();
     }
}

2 votes

- mais le xor (^) est un mauvais combinateur pour les codes de hachage, car il conduit souvent à de nombreuses collisions diagonales (c'est-à-dire {"foo", "bar"} vs {"bar", "foo"}. Un meilleur choix est de multiplier et d'ajouter chaque terme - c'est-à-dire 17 * a.GetHashCode() + B.GetHashCode();

2 votes

Marc, je vois ce que tu veux dire. Mais comment arrives-tu au nombre magique 17? Est-il avantageux d'utiliser un nombre premier comme multiplicateur pour combiner des hachages? Si oui, pourquoi?

0 votes

Puis-je suggérer de renvoyer : (A + B).GetHashCode() plutôt que : 17 * A.GetHashCode() + B.GetHashCode() Cela permettra : 1) D'avoir moins de risques de collision et 2) Assurer qu'il n'y a pas de débordement d'entiers.

1voto

Behzad Ebrahimi Points 50

Que dire de la classe Hashtable !

Hashtable oMyDic = new Hashtable();
Object oAnyKeyObject = null;
Object oAnyValueObject = null;
oMyDic.Add(oAnyKeyObject, oAnyValueObject);
foreach (DictionaryEntry de in oMyDic)
{
   // Faites votre travail
}

De cette manière, vous pouvez utiliser n'importe quel objet (votre objet de classe) comme clé de dictionnaire générique :)

1voto

kb4000 Points 100

J'avais le même problème. Maintenant, je peux utiliser n'importe quel objet que j'ai essayé comme clé en écrasant Equals et GetHashCode.

Voici une classe que j'ai construite avec des méthodes à utiliser à l'intérieur des remplacements d'Equals(object obj) et GetHashCode(). J'ai décidé d'utiliser des génériques et un algorithme de hachage qui devrait pouvoir couvrir la plupart des objets. Veuillez me faire savoir si vous voyez quelque chose ici qui ne fonctionne pas pour certains types d'objets et si vous avez un moyen de l'améliorer.

public class Equality
{
    public int GetHashCode(T classInstance)
    {
        List fields = GetFields();

        unchecked
        {
            int hash = 17;

            foreach (FieldInfo field in fields)
            {
                hash = hash * 397 + field.GetValue(classInstance).GetHashCode();
            }
            return hash;
        }
    }

    public bool Equals(T classInstance, object obj)
    {
        if (ReferenceEquals(null, obj))
        {
            return false;
        }
        if (ReferenceEquals(this, obj))
        {
            return true;
        }
        if (classInstance.GetType() != obj.GetType())
        {
            return false;
        }

        return Equals(classInstance, (T)obj);
    }

    private bool Equals(T classInstance, T otherInstance)
    {
        List fields = GetFields();

        foreach (var field in fields)
        {
            if (!field.GetValue(classInstance).Equals(field.GetValue(otherInstance)))
            {
                return false;
            }
        }

        return true;
    }

    private List GetFields()
    {
        Type myType = typeof(T);

        List fields = myType.GetTypeInfo().DeclaredFields.ToList();
        return fields;
    }
}

Voici comment cela est utilisé dans une classe:

public override bool Equals(object obj)
{
    return new Equality().Equals(this, obj);
}

public override int GetHashCode()
{
    unchecked
    {
        return new Equality().GetHashCode(this);
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X