40 votes

Préférer EqualityComparer<T> à IEqualityComparer<T>

De la IEqualityComparer<T> section des remarques sur MSDN :

  1. Nous vous recommandons de dériver du classe EqualityComparer<T> au lieu de d'implémenter l'interface IEqualityComparer<T>. car la classe classe EqualityComparer<T> teste l'égalité en utilisant l'égalité à l'aide de la fonction IEquatable<T>.Equals au lieu de la méthode la méthode Object.Equals. ...

    • Je ne comprends pas l'argument de la citation, à savoir pourquoi nous devrions préférer dériver de EqualityComparer<T> au lieu de mettre en œuvre la classe IEqualityComparer<T> . Cela implique que les objets mettant en œuvre IEqualityComparer<T> testera l'égalité en utilisant Object.Equals mais n'est-ce pas le but de mettre en œuvre IEqualityComparer<T> quand nous ne voulons pas tester l'égalité en utilisant Object.Equals ou IEquatable<T>.Equals ?

    • Cela implique également que si nous dérivons de EqualityComparer<T> alors la classe dérivée testera l'égalité en utilisant IEquatable<T>.Equals méthode. Encore une fois, n'est-ce pas le but de dériver de EqualityComparer<T> quand nous ne voulons pas tester l'égalité en utilisant Object.Equals ou IEquatable<T>.Equals (depuis EqualityComparer<T>.Default déjà testé en utilisant Object.Equals ou IEquatable<T>.Equals ) ?

  2. ... Ceci est cohérent avec les règles Contient, IndexOf, LastIndexOf et Remove de la classe Dictionary<TKey, TValue> et d'autres collections génériques.

    • Je suppose que la plupart des collections de la bibliothèque .NET testent pour par défaut l'égalité des éléments (c'est-à-dire lorsque les utilisateurs ne fournissent pas leurs propres données personnalisées). IEqualityComparer<T> à ces collections) en appelant IEquatable<T>.Equals ou Object.Equals (selon que les éléments de type T mettre en œuvre IEquatable<T> ) via EqualityComparer<T>.Default .

    • Pourquoi ces collections ne sont-elles pas (lors des tests de par défaut égalité) appeler IEquatable<T>.Equals ou Object.Equals directement au lieu de passer par EqualityComparer<T>.Default classe ?

2 votes

Vous avez donné des citations, mais sans dire d'où elles venaient, ce qui rend plus difficile la compréhension du contexte...

3 votes

4 votes

+1 pour la première question, je me souviens avoir lu cela dans les docs et ne pas l'avoir compris, mais je l'ai fait passer comme sans importance.

27voto

Jeff Mercado Points 42075

En ce qui concerne votre première question :

La section des remarques pour le IEqualityComparer<T> ne semble pas vraiment fournir une raison pour laquelle vous devriez préférer dériver de la classe abstraite plutôt que de l'interface, cela ressemble plus à une raison pour laquelle l'interface de comparaison d'égalité existe en premier lieu. Ce qui y est dit est pratiquement inutile, il s'agit essentiellement de décrire ce que fait l'implémentation par défaut. En fait, le "raisonnement" qu'ils ont fourni ici ressemble plus à une directive sur ce que vos comparateurs doivent faire. pourrait et n'est pas pertinent par rapport à ce qu'il fait réellement.

En regardant l'interface publique/protégée de l'application EqualityComparer<T> n'a qu'une seule qualité : elle implémente la classe non générique IEqualityComparer interface. Je pense que ce qu'ils voulaient dire, c'est qu'ils recommandent d'en dériver parce que EqualityComparer<T> implémente en fait la fonction non générique IEqualityComparer de sorte que votre classe puisse être utilisée lorsque le comparateur non générique est requis.

Il est plus logique dans la section des remarques pour IComparer<T> :

Nous vous recommandons de dériver du Comparer<T> au lieu de mettre en œuvre la classe IComparer<T> car l'interface Comparer<T> fournit une implémentation explicite de l'interface IComparer.Compare et la méthode Default qui récupère le comparateur par défaut de l'objet.

Je suppose que c'était censé dire quelque chose de similaire pour IEqualityComparer<T> mais certaines idées ont été mélangées et on s'est retrouvé avec une description incomplète.


En ce qui concerne votre deuxième question :

L'un des principaux objectifs des collections de la bibliothèque était d'être aussi flexible que possible. L'un des moyens d'y parvenir est d'autoriser des méthodes personnalisées de comparaison d'objets en leur sein en fournissant une fonction IComparer<T> ou IEqualityComparer<T> pour faire les comparaisons. Il serait beaucoup plus facile d'obtenir une instance d'un comparateur par défaut lorsqu'il n'en existe pas que d'effectuer les comparaisons directement. Ces comparateurs, à leur tour, pourraient inclure la logique nécessaire pour appeler les comparaisons appropriées, joliment emballées.

Par exemple, les comparateurs par défaut peuvent déterminer si T met en œuvre IEquatable<T> et appeler IEquatable<T>.Equals sur l'objet ou utiliser autrement Object.Equals . Il est préférable de l'encapsuler ici dans le comparateur que de le répéter potentiellement dans le code des collections.

En outre, s'ils voulaient se rabattre sur l'appel IEquatable<T>.Equals directement, il leur faudrait ajouter une contrainte sur les T qui rendrait cet appel possible. Cela rend le système moins flexible et annule les avantages de fournir le comparateur en premier lieu.

1 votes

J'aime votre idée de la mise en œuvre de l'interface non générique - j'aurais aimé que MSDN l'exprime aussi clairement. Dans la plupart des cas, je ne pense pas que ce soit un avantage significatif, mais c'est un bon point :)

0 votes

Superbe article et je le marquerai comme réponse, que vous répondiez ou non à cette question : avais-je raison de supposer que nous dérivons habituellement de EqualityComparer<T>/EqualityComparer<T> lorsque nous ne voulons pas tester l'égalité de T en utilisant Object.Equals ou IEquatable<T>.Equals ?

1 votes

@flock : J'ai ajouté ma réponse à votre deuxième question.

4voto

Jon Skeet Points 692016

Je ne comprends pas la suggestion de 1. Elle me semble tout à fait étrange.

Quant au point 2, très souvent, on se retrouve avec un type (tel que Dictionary ) qui a un IEqualityComparer<T> . Bien que la mise en œuvre pourrait stocker une valeur nulle et appeler explicitement Equals en soi, il serait fastidieux de le faire - et cela impliquerait également une laideur significative pour s'assurer qu'il n'y a pas de boîte de types de valeur mettant en œuvre des IEquatable<T> inutilement. L'utilisation de l'interface an EqualityComparer<T>.Default es de manière significative plus simple et plus cohérente.

2 votes

"Je ne comprends pas la suggestion de 1. Elle me semble tout à fait étrange." Je ne suis pas tout à fait sûr de ce que vous ne comprenez pas : les arguments avancés par MSDN quote ou avec mon raisonnement pourquoi MSDN quote n'a pas de sens ? Dans le premier cas, êtes-vous d'accord avec mon raisonnement pour dire que la citation n'a pas de sens ?

6 votes

@flockofcode : MSDN. Les arguments ne font pas sens - je ne vois aucune raison de préférer la dérivation de EqualityComparer<T> plutôt que de mettre en œuvre l'interface directement.

0 votes

Puis-je également demander : "Alors que l'implémentation pourrait stocker une valeur nulle..." a) Que voulez-vous dire par stocker une valeur nulle ? b) "... et appeler explicitement Equals lui-même..." Je suppose que vous voulez dire que l'implémentation appellerait explicitement IEquatable<>.Equals de T ?

0voto

HaraldDutch Points 225

La principale raison de dériver une classe à partir d'une classe de base est que la base peut fournir du code que vous pouvez réutiliser, de sorte que vous n'avez pas à l'écrire vous-même. vous-même.

Si vous deviez dériver votre comparateur de l'interface, vous devriez créer le fichier qui vous donne un comparateur par défaut (bien sûr seulement si vous en avez besoin, mais bon, tout le monde veut une fonctionnalité gratuite !)

Classe EqualityComparer utilise le modèle de conception d'usine .

Dans le modèle Factory, nous créons un objet sans exposer la logique de création au client et nous faisons référence à l'objet nouvellement créé en utilisant une interface commune.

Ce qui est bien, c'est que tous les utilisateurs de EqualityComparer n'ont qu'à appeler prperty default, et tout est fait pour qu'ils créent l'objet approprié qui expose l'interface IEqualtiyComparer

L'avantage de cette méthode est que si vous avez besoin d'un IEqualityComparer comme paramètre dans une fonction, vous n'avez pas à vérifier si la classe T met en œuvre IEqualtiy<T> ou non, le dictionnaire le fait pour vous.

Si vous dérivez de EqualtityComparer<T> et assurez-vous que la classe dérivée suit le modèle de conception de la fabrique. il est alors facile de passer d'un comparateur d'égalité à un autre.

En outre, comme pour toute usine, il suffit de modifier les paramètres de l'usine pour qu'elle produise des comparateurs d'égalité complètement différents.

Bien sûr, vous pouvez créer une usine de comparaison d'égalité sans dériver de EqualtyComparer<T> mais si vous le faites, votre usine peut créer un type supplémentaire de comparateurs d'égalité : le comparateur d'égalité par défaut, qui est celui qui utilise eiether IEquatable<T> ou Object.Equals. Vous n'avez pas besoin d'écrire de code supplémentaire pour cela, il suffit de dériver !

Que vous trouviez utile de dériver de EqualtyComparer ou non, dépend de l'utilité que vous accordez au modèle de conception "factory".

À titre d'exemple, supposons que vous souhaitiez vérifier l'égalité de deux dictionnaires. On peut imaginer plusieurs niveaux d'égalité :

  1. Les dictionnaires X et Y sont égaux s'ils sont le même objet.
  2. X et Y sont égaux s'ils ont des clés égales (en utilisant le comparateur de clés du dictionnaire), et si leurs valeurs sont le même objet.
  3. X et Y sont égaux s'ils ont des clés égales (en utilisant le comparateur de clés de dictionnaire), et si leurs valeurs sont égales en utilisant le comparateur d'égalité par défaut de l'option TValue
  4. X et Y sont égaux s'ils ont des clés égales (en utilisant le comparateur de clés du dictionnaire), et des valeurs égales en utilisant un comparateur d'égalité fourni pour les valeurs.

Si vous dérivez votre classe de comparateur de dictionnaire de EqualityComparer, vous avez déjà le comparateur (1). Si le comparateur de valeurs TV fourni est dérivé de EqualityComparer, il n'y a pas de réelle différence entre (3) et (4).

Déterminons donc la fabrique qui peut créer ces quatre comparateurs :

class DictionaryComparerFactory<TKey, TValue> : 
    EqualitiyComparer<Dictionary<TKey, TValue>>
{
    // By deriving from EqaulityComparer, you already have comparer (1)
    // via property Default

    // comparer (4):
    // X and Y are equal if equal keys and equal values using provided value comparer
    public static IEqualityComparer<Dictionary<TKey, TValue>>
        CreateContentComparer(IEqualityComparer<TValue> valueComparer)
    {
        return new DictionaryComparer<TKey, TValue>(valueComparer);
    }

    // comparer (3): X and Y equal if equal keys and values default equal
    // use (4) by providing the default TValue comparer
    public static IEqualityComparer<Dictionary<TKey, TValue>>
        CreateDefaultValueComparer(IEqualityComparer<TValue> valueComparer)
    {
        IEqualityComparer<TValue> defaultValueComparer =
            EqualtiyComparer<TValue>.Default;
        return new DictionaryComparer<TKey, TValue>(defaultValuecomparer);
    }

    // comparer (2): X and Y are equal if equal keys and values are same object
    // use reference equal for values
    public IEqualityComparer<TKey, TValue> CreateReferenceValueComparer()
    {
        IEqualityComparer<TValue> referenceValueComparer = ...
        return new DictionaryComparer<TKey, TValue>(referenceValuecomparer);
    }
}

Pour le comparateur (2), vous pouvez utiliser le comparateur de valeurs de référence comme décrit dans stackoverflow. IEqualityComparer qui utilise ReferenceEquals

Nous avons donc maintenant quatre comparateurs d'égalité différents en ne fournissant le code que pour un seul comparateur. Le reste est réutilisé !

Cette réutilisation n'est pas aussi facile sans une usine qui crée des comparateurs par défaut.

Code pour le comparateur (4) : utiliser un comparateur fourni pour vérifier l'égalité pour TValue

// constructor
protected DictionaryComparer(IEqualityComparer<TValue> valueComparer) : base()
{   // if no comparer provided, use the default comparer
    if (Object.ReferenceEquals(valueComparer, null))
        this.valueComparer = EqualityComparer<TValue>.Default;
    else
        this.valueComparer = valueComparer
}

// comparer for TValue initialized in constructor
protected readonly IEqualityComparer<TValue> valueComparer;

public override bool Equals(Dictionary<TKey, TValue> x, Dictionary<TKey, TValue> y)
{
    if (x == null) { return y == null; } 
    if (y == null) return false;
    if (Object.ReferenceEquals(x, y)) return true;
    if (x.GetType() != y.GetType()) return false;

    // now do equality checks according to (4)
    foreach (KeyValuePair<TKey, TValue> xKeyValuePair in x)
    {
        TValue yValue;
        if (y.TryGetValue(xKeyValuePair.Key, out yValue))
        {   // y also has x.Key. Are values equal?
            if (!this.valueComparer.Equals(xKeyValuePair.Value, yValue))
            {   // values are not equal
                return false;
            }
            // else: values equal, continue with next key
        }
        else
        {   // y misses a key that is in x
            return false;
        }
    }

    // if here, all key/values equal
    return true;
}

Maintenant, nous pouvons simplement comparer deux dictionnaires en utilisant des comparateurs différents :

var dictionaryX = ...
var dictionaryY = ...

var valueComparer1 = ...
var valueComparer2 = ...

var equalityComparer1 = DictionaryComparer<...>.Default();
var equalityComparer2 = DictionaryComparer<...>..CreateDefaultValueComparer();
var equalityComparer3 = DictionaryComparer<...>.CreatereferenceValueComparer();
var equalityComparer4 = DictionaryComparer<...>
   .CreateContentComparer(valueCompaerer1);
var equalityComparer5 = DictionaryComparer<...>
   .CreateContentComparer(valueCompaerer2);

Ainsi, la dérivation fait que mes usines de comparaison d'égalité ont toujours un comparateur Defautlt approprié. Cela m'évite d'avoir à écrire le code moi-même

0voto

Wouter Points 773

Si vous changez un seul mot dans l'explication du MSDN, c'est-à-dire découler de en utiliser ça a beaucoup plus de sens.

Sur MSDN : EqualityComparer<T>

Nous vous recommandons de (ne pas dériver de) utiliser le site EqualityComparer<T> au lieu de mettre en œuvre la classe IEqualityComparer<T> car l'interface EqualityComparer<T> teste l'égalité en utilisant la classe IEquatable<T>.Equals au lieu de la méthode Object.Equals méthode. Ceci est cohérent avec la Contains , IndexOf , LastIndexOf y Remove de la classe Dictionary et d'autres collections génériques.

Bien sûr, cela ne fonctionne que si T implémente IEquality<T>

Notez que, curieusement, seuls Array y List<T> tienen IndexOf y LastIndexOf et il n'y a pas de surcharges qui prennent une méthode IEqualityComparer<T> pour l'une ou l'autre des méthodes. Alors que d'autres collections génériques ont un constructeur qui prend un fichier IEqualityComparer<T>

Sur MSDN : Comparer<T>:

Nous vous recommandons de (ne pas dériver de) utiliser de la Comparer<T> au lieu de mettre en œuvre la classe IComparer<T> car l'interface Comparer<T> fournit une implémentation explicite de l'interface IComparer.Compare et la propriété Default qui permet d'obtenir le comparateur par défaut de l'objet.

Bien sûr, cela ne fonctionne que si T implémente IComparable ou IComparable<T>

Si T n'implémente pas les interfaces requises dérivant de EqualityComparer<T> ou Comparer<T> est utile car il fournit gratuitement une implémentation pour les interfaces non génériques.

D'autre part, la mise en œuvre IEqualityComparer<T> ou IComparer<T> peut avoir un avantage en termes de performance car il peut sauter les appels à IEquatable<T> ou IComparable<T> .

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