12
votes

IequalityComparer générique et gethashcode

Être un peu paresseux à propos de la mise en œuvre de nombreux iéquitycomparers et, étant donné que je ne pouvais pas facilement modifier les implémentations de classe d'objet par rapport, je suis allé avec ce qui suit, destiné à être utilisé avec des méthodes distinctes () et sauf (). :

public class GenericEqualityComparer<T> : IEqualityComparer<T>
{
    Func<T, T, bool> compareFunction;
    Func<T, int> hashFunction;

    public GenericEqualityComparer(Func<T, T, bool> compareFunction, Func<T, int> hashFunction)
    {
        this.compareFunction = compareFunction;
        this.hashFunction = hashFunction;
    }

    public bool Equals(T x, T y)
    {
        return compareFunction(x, y);
    }

    public int GetHashCode(T obj)
    {
        return hashFunction(obj);
    }
}


1 commentaires

Je suppose que si vous l'avez fait et que vous avez utilisé la classe en question dans un dictionnaire, vous vous retrouveriez avec des performances assez mauvaises lorsque vous faites une recherche.


6 Réponses :


13
votes

Rien ne irait mal, mais dans des conteneurs à base de table de hachage, vous allez d'environ O (1) à O (n) performances lors de la recherche. Vous feriez mieux de stocker simplement tout dans une liste et une force brute qui la recherche pour les articles qui remplissent l'égalité.


1 commentaires

Ok, c'est ce que je pensais. Ce n'est pas vraiment un problème que je suis sauf énumérables avec des milliers d'objets, et pas très souvent. Je suis un peu déçu, j'espérais des problèmes plus gênants;)



1
votes

Votre performance descendra dans le drain. distinct et sauf sont des opérations efficaces lors de la mise en œuvre sur les structures de données définies. En fournissant une valeur de hachage constante, vous détruisez essentiellement cette algorithme naïf caractéristique et forcée à l'aide d'une recherche linéaire.

Vous devez voir si cela est acceptable pour votre volume de données. Mais pour des ensembles de données quelque peu plus importants, la différence sera prononcée. Par exemple, sauf passera du temps prévu O ( n ) à O ( n ²), ce qui peut être une grosse affaire.

Plutôt que de fournir une constante, pourquoi pas seulement appeler la méthode gethashcode ? Il peut ne pas donner une valeur particulièrement bonne, mais elle ne peut pas être pire que d'utiliser une constante, et la correction de l'exactitude ne sera toujours préservée que si la méthode gethashcode de l'objet est remplacée pour renvoyer les mauvaises valeurs.


3 commentaires

Contexte: Listes avec 1000 éléments, sans nécessité de mettre en œuvre de nombreux comparateurs d'égalité et de donner une fonction de hachage semblait surkill, pour ce cas précis ;)


@Mathieu bien, allez-y. Mais j'utiliserais vraiment le gethashcode avant une constante.


bien sûr. Mais je comparais des objets qui ne remplacent pas Iequatable. Et mis à part cet étui d'utilisation précis, ils n'ont pas besoin aussi. Et ce comparateur est mis en œuvre en privé dans ma classe, il ne devrait donc pas être mal utilisé.



10
votes

Si un cas d'utilisation commun est comparant des objets selon l'une de leurs propriétés, vous pouvez ajouter un constructeur supplémentaire et mettre en œuvre et l'appeler comme ceci:

public static GenericEqualityComparer<T> Create<TValue>(Func<T, TValue> projection)
{
    return new GenericEqualityComparer<T>(
        (t1, t2) => EqualityComparer<TValue>.Default.Equals( projection(t1), projection(t2)),
        t => EqualityComparer<TValue>.Default.GetHashCode(projection(t)));
}

var comparer = GenericEqualityComparer<YourObjectType>.Create( o => o.PropertyToCompare); 


5 commentaires

Celui-ci est gentil, mais je dois comparer 2 propriétés


@Mathieu: Peut-être que HIS fonctionnerait: NOUVEAU GENERICEQUALITYCOMERER (O => Nouveau TUPLE (O.P1, O.P2));


Vous feriez mieux d'utiliser une deuxième générique (pour la valeur projetée) et d'utiliser Equalitalcomparer .default pour exécuter des égaux et gethascode; Sinon, vous obtenez une boxe et des problèmes avec NULL.


@Marc: Merci pour la précieuse contribution. Je comprends la question de la boxe, mais quels problèmes avec NULL voyez-vous? Si les objets à comparer peuvent être nuls, cela devrait être géré par la Lambda qui est transmis au constructeur.


@Henrik - appelant .gethashcode () , par exemple; p.e. foo => foo.name est risqué. Le comparateur d'égalité intégré évite ceci (et évite la boxe et prend en charge Iequatable et prend en charge nullable )



1
votes

a trouvé celui-ci sur codeProject - un iéquitycalérateur générique pour LINQ distinct () bien fait


2 commentaires

C'était ce que je cherchais!


Il y aura une performance frappée par l'utilisation de la réflexion.



-1
votes

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


1 commentaires

Qu'est-ce qui est si mauvais à ce sujet? (Sauf le si (vrai) retourne vrai; sinon retourne faux; chose)



0
votes

J'ai besoin de réécrire la solution Henrik en tant que classe implémentant IequalityComparer qui donne ceci: xxx


0 commentaires