Comment utiliser IEqualityComparer

97

J'ai quelques cloches dans ma base de données avec le même numéro. Je veux tous les obtenir sans duplication. J'ai créé une classe de comparaison pour faire ce travail, mais l'exécution de la fonction provoque un gros retard de la fonction sans distinction, de 0,6 sec à 3,2 sec!

Est-ce que je fais les choses correctement ou dois-je utiliser une autre méthode?

reg.AddRange(
    (from a in this.dataContext.reglements
     join b in this.dataContext.Clients on a.Id_client equals b.Id
     where a.date_v <= datefin && a.date_v >= datedeb
     where a.Id_client == b.Id
     orderby a.date_v descending 
     select new Class_reglement
     {
         nom  = b.Nom,
         code = b.code,
         Numf = a.Numf,
     })
    .AsEnumerable()
    .Distinct(new Compare())
    .ToList());

class Compare : IEqualityComparer<Class_reglement>
{
    public bool Equals(Class_reglement x, Class_reglement y)
    {
        if (x.Numf == y.Numf)
        {
            return true;
        }
        else { return false; }
    }
    public int GetHashCode(Class_reglement codeh)
    {
        return 0;
    }
}
Akrem
la source
16
Vous voudrez peut-être jeter un oeil aux directives et règles pour GetHashCode
Conrad Frix
Ce blog explique comment utiliser parfaitement IEqualityComparer: blog.alex-turok.com/2013/03/c-linq-and-iequalitycomparer.html
Jeremy Ray Brown

Réponses:

173

Votre GetHashCodeimplémentation renvoie toujours la même valeur. Distinctrepose sur une bonne fonction de hachage pour fonctionner efficacement car elle crée en interne une table de hachage .

Lors de l'implémentation d'interfaces de classes, il est important de lire la documentation , pour savoir quel contrat vous êtes censé implémenter. 1

Dans votre code, la solution est de transmettre GetHashCodeà Class_reglement.Numf.GetHashCodeet mettre en œuvre de manière appropriée là.

En dehors de cela, votre Equalsméthode est pleine de code inutile. Il pourrait être réécrit comme suit (même sémantique, ¼ du code, plus lisible):

public bool Equals(Class_reglement x, Class_reglement y)
{
    return x.Numf == y.Numf;
}

Enfin, l' ToListappel est inutile et prend du temps: AddRangeaccepte tout, IEnumerabledonc la conversion en un Listn'est pas nécessaire. AsEnumerableest également redondant ici car le traitement du résultat en AddRangeprovoquera de toute façon cela.


1 Écrire du code sans savoir ce qu'il fait réellement s'appelle la programmation culte du fret . C'est une pratique étonnamment répandue. Cela ne fonctionne fondamentalement pas.

Konrad Rudolph
la source
19
Votre égal échoue lorsque x ou y est nul.
dzendras
4
@dzendras Idem pour GetHashCode. Cependant, notez que la documentation deIEqualityComparer<T> ne spécifie pas quoi faire avec les nullarguments - mais les exemples fournis dans l'article ne gèrent pas non nullplus.
Konrad Rudolph
49
Sensationnel. L'abomination est inutilement dure. Nous sommes ici pour nous entraider, pas pour insulter. Je suppose que cela fait rire certaines personnes, mais je recommanderais de le supprimer.
Jess
4
+1 pour m'avoir fait lire sur "la programmation culte de la cargaison" sur wiki et ensuite changer mon slogan skype en "// La magie profonde commence ici ... suivie par une magie lourde".
Alex
4
@NeilBenn Vous confondez des conseils francs avec de la grossièreté. Depuis OP a accepté la réponse (et, je pourrais le noter, dans une version beaucoup plus sévère!), Ils ne semblaient pas faire la même erreur. Je ne sais pas pourquoi vous pensez que donner des conseils est impoli, mais vous vous trompez lorsque vous dites que «le gars n'a pas besoin de cours». Je ne suis pas du tout d'accord: la conférence était nécessaire et elle était prise au sérieux. Le code, tel qu'il est rédigé, était mauvais et basé sur de mauvaises pratiques de travail. Ne pas le signaler serait un mauvais service, et pas du tout utile, car le PO ne pouvait pas améliorer leur fonctionnement.
Konrad Rudolph le
47

Essayez ce code:

public class GenericCompare<T> : IEqualityComparer<T> where T : class
{
    private Func<T, object> _expr { get; set; }
    public GenericCompare(Func<T, object> expr)
    {
        this._expr = expr;
    }
    public bool Equals(T x, T y)
    {
        var first = _expr.Invoke(x);
        var sec = _expr.Invoke(y);
        if (first != null && first.Equals(sec))
            return true;
        else
            return false;
    }
    public int GetHashCode(T obj)
    {
        return obj.GetHashCode();
    }
}

Un exemple de son utilisation serait

collection = collection
    .Except(ExistedDataEles, new GenericCompare<DataEle>(x=>x.Id))
    .ToList(); 
Suneelsarraf
la source
19
GetHashCodedoit également utiliser l'expression: return _expr.Invoke(obj).GetHashCode();voir cet article pour une utilisation de celui-ci.
orad
3

Juste du code, avec implémentation GetHashCodeet NULLvalidation:

public class Class_reglementComparer : IEqualityComparer<Class_reglement>
{
    public bool Equals(Class_reglement x, Class_reglement y)
    {
        if (x is null || y is null))
            return false;

        return x.Numf == y.Numf;
    }

    public int GetHashCode(Class_reglement product)
    {
        //Check whether the object is null 
        if (product is null) return 0;

        //Get hash code for the Numf field if it is not null. 
        int hashNumf = product.hashNumf == null ? 0 : product.hashNumf.GetHashCode();

        return hashNumf;
    }
}

Exemple: liste de Class_reglement distincte par Numf

List<Class_reglement> items = items.Distinct(new Class_reglementComparer());
Shahar Shokrani
la source
2

L'inclusion de votre classe de comparaison (ou plus précisément de l' AsEnumerableappel que vous deviez utiliser pour la faire fonctionner) signifiait que la logique de tri est passée du statut basé sur le serveur de base de données à celui du client de base de données (votre application). Cela signifie que votre client doit maintenant récupérer puis traiter un plus grand nombre d'enregistrements, ce qui sera toujours moins efficace que d'effectuer la recherche sur la base de données où les index appropriés peuvent être utilisés.

Vous devriez plutôt essayer de développer une clause where qui répond à vos exigences, consultez Utilisation d'un IEqualityComparer avec une clause LINQ to Entities Except pour plus de détails.

Justin
la source
2

Si vous voulez une solution générique sans boxe:

public class KeyBasedEqualityComparer<T, TKey> : IEqualityComparer<T>
{
    private readonly Func<T, TKey> _keyGetter;

    public KeyBasedEqualityComparer(Func<T, TKey> keyGetter)
    {
        _keyGetter = keyGetter;
    }

    public bool Equals(T x, T y)
    {
        return EqualityComparer<TKey>.Default.Equals(_keyGetter(x), _keyGetter(y));
    }

    public int GetHashCode(T obj)
    {
        TKey key = _keyGetter(obj);

        return key == null ? 0 : key.GetHashCode();
    }
}

public static class KeyBasedEqualityComparer<T>
{
    public static KeyBasedEqualityComparer<T, TKey> Create<TKey>(Func<T, TKey> keyGetter)
    {
        return new KeyBasedEqualityComparer<T, TKey>(keyGetter);
    }
}

usage:

KeyBasedEqualityComparer<Class_reglement>.Create(x => x.Numf)
user764754
la source
0

IEquatable<T> peut être un moyen beaucoup plus simple de le faire avec des frameworks modernes.

Vous obtenez une bool Equals(T other)fonction simple et agréable et il n'y a pas de problème avec le casting ou la création d'une classe séparée.

public class Person : IEquatable<Person>
{
    public Person(string name, string hometown)
    {
        this.Name = name;
        this.Hometown = hometown;
    }

    public string Name { get; set; }
    public string Hometown { get; set; }

    // can't get much simpler than this!
    public bool Equals(Person other)
    {
        return this.Name == other.Name && this.Hometown == other.Hometown;
    }

    public override int GetHashCode()
    {
        return Name.GetHashCode();  // see other links for hashcode guidance 
    }
}

Notez que vous devez implémenter GetHashCodesi vous utilisez ceci dans un dictionnaire ou avec quelque chose comme Distinct.

PS. Je ne pense pas que les méthodes Equals personnalisées fonctionnent avec le framework d'entité directement du côté de la base de données (je pense que vous le savez parce que vous faites AsEnumerable) mais c'est une méthode beaucoup plus simple pour faire un simple Equals pour le cas général.

Si les choses ne semblent pas fonctionner (comme des erreurs de clé en double lors de ToDictionary), placez un point d'arrêt dans Equals pour vous assurer qu'il est atteint et assurez-vous que vous avez GetHashCodedéfini (avec le mot-clé override).

Simon_Weaver
la source
1
Vous devez toujours vérifier la valeur nulle
disklosr
Je n'ai jamais rencontré ça mais je me souviendrai de le faire la prochaine fois. Avez-vous un null dans un List <T> ou quelque chose comme ça?
Simon_Weaver
1
Sous la .Equals()méthode que vous semblez avoir comparé other.Hometownà lui-même, au lieu dethis.Hometown
Jake Stokes
Oups. Correction d'une faute de frappe :)
Simon_Weaver