Quelle est la différence entre IEquatable et la substitution de Object.Equals ()?

185

Je veux que ma Foodclasse puisse tester chaque fois qu'elle est égale à une autre instance de Food. Je l'utiliserai plus tard contre une liste, et je veux utiliser sa List.Contains()méthode. Dois-je mettre en œuvre IEquatable<Food>ou simplement remplacer Object.Equals()? Depuis MSDN:

Cette méthode détermine l'égalité à l'aide du comparateur d'égalité par défaut, tel que défini par l'implémentation de l'objet de la méthode IEquatable.Equals pour T (le type de valeurs dans la liste).

Ma prochaine question est donc la suivante: quelles fonctions / classes du framework .NET utilisent Object.Equals()? Dois-je l'utiliser en premier lieu?

dévoré d'elysium
la source
3
Une très bonne explication ici blogs.msdn.com/b/jaredpar/archive/2009/01/15/…
nawfal
duplicata possible de Understanding IEquatable
nawfal

Réponses:

214

La principale raison est la performance. Lorsque les médicaments génériques ont été introduits dans .NET 2.0 , ils ont été en mesure d'ajouter un tas de classes soignées telles que List<T>, Dictionary<K,V>, HashSet<T>, etc. Ces structures font un usage intensif de GetHashCodeet Equals. Mais pour les types de valeur, cela nécessitait de la boxe. IEquatable<T>permet à une structure d'implémenter une Equalsméthode fortement typée donc aucune boxe n'est requise. Ainsi, de bien meilleures performances lors de l'utilisation de types valeur avec des collections génériques.

Les types de référence ne bénéficient pas autant, mais l' IEquatable<T>implémentation vous permet d'éviter un cast System.Objectdont peut faire la différence s'il est appelé fréquemment.

Comme indiqué sur le blog de Jared Parson , vous devez toujours implémenter les remplacements d'objets.

Josh
la source
Y a-t-il un cast entre les types de référence? J'ai toujours pensé que les transtypages n'étaient que des "déclarations" que vous faites au compilateur lorsque vous attribuez des transtypages non évidents d'un type d'objet à un autre. C'est, après avoir compilé, que le code ne saurait même pas qu'il y avait un casting là-bas.
dévoré de l'elysium le
7
C'est vrai en C ++ mais pas en langages .NET qui appliquent la sécurité de type. Il y a un cast à l'exécution et si le cast échoue, une exception est levée. Il y a donc une petite pénalité d'exécution à payer pour le casting. Le compilateur peut optimiser les upcasts Par exemple object o = (object) "string"; Mais downcasting - string s = (string) o; - doit se produire lors de l'exécution.
Josh
1
Je vois. Par hasard, vous avez un endroit où je peux obtenir ce genre d'informations "plus profondes" sur .NET? Merci!
dévoré d'elysium le
7
Je recommanderais CLR via C # de Jeff Richter et C # in Depth par Jon Skeet. En ce qui concerne les blogs, les blogs Wintellect sont bons, les blogs msdn, etc.
Josh
L' IEquatable<T>interface fait-elle autre chose que rappeler à un développeur d'inclure un public bool Equals(T other) membre dans la classe ou la structure? La présence ou l'absence de l'interface ne fait aucune différence au moment de l'exécution. La surcharge de Equalssemble être tout ce qui est nécessaire.
mikemay
48

Selon le MSDN :

Si vous implémentez IEquatable<T>, vous devez également remplacer les implémentations de classe de base de Object.Equals(Object)et GetHashCode afin que leur comportement soit cohérent avec celui de la IEquatable<T>.Equals méthode. Si vous remplacez Object.Equals(Object), votre implémentation remplacée est également appelée dans les appels à la Equals(System.Object, System.Object)méthode statique de votre classe. Cela garantit que toutes les invocations de la Equalsméthode renvoient des résultats cohérents.

Il semble donc qu'il n'y ait pas de réelle différence fonctionnelle entre les deux, sauf que l'un ou l'autre pourrait être appelé en fonction de la façon dont la classe est utilisée. Du point de vue des performances, il est préférable d'utiliser la version générique car aucune pénalité de boxe / déballage n'est associée.

D'un point de vue logique, il est également préférable d'implémenter l'interface. Remplacer l'objet ne dit vraiment à personne que votre classe est en fait égalable. Le remplacement peut simplement être une classe ne rien faire ou une implémentation superficielle. L'utilisation de l'interface dit explicitement, "Hé, cette chose est valable pour la vérification d'égalité!" C'est juste un meilleur design.

CodexArcanum
la source
9
Les structures doivent absolument implémenter iEquatable (de leurOwnType) si elles doivent être utilisées comme clés dans un dictionnaire ou une collection similaire; il offrira un gain de performances majeur. Les classes non héritables recevront une légère amélioration des performances en implémentant IEquatable (of theirOwnType). Les classes héritables ne doivent // pas // implémenter IEquatable.
supercat
30

Prolonger ce que Josh a dit avec un exemple pratique. +1 à Josh - J'étais sur le point d'écrire la même chose dans ma réponse.

public abstract class EntityBase : IEquatable<EntityBase>
{
    public EntityBase() { }

    #region IEquatable<EntityBase> Members

    public bool Equals(EntityBase other)
    {
        //Generic implementation of equality using reflection on derived class instance.
        return true;
    }

    public override bool Equals(object obj)
    {
        return this.Equals(obj as EntityBase);
    }

    #endregion
}

public class Author : EntityBase
{
    public Author() { }
}

public class Book : EntityBase
{
    public Book() { }
}

De cette façon, j'ai la méthode Equals () réutilisable qui fonctionne hors de la boîte pour toutes mes classes dérivées.

ce. __curious_geek
la source
Encore une question. Quel est l'avantage d'utiliser "obj as EntityBase" au lieu de (EntityBase) obj? Juste une question de style ou y a-t-il un avantage?
dévoré l'elysium le
22
dans le cas de "obj as EntityBase" - si obj n'est pas de type EntityBase, il passera "null" et continuera sans aucune erreur ou exception, mais en cas de "(EntityBase) obj", il essaiera de force de lancer l'obj à EntityBase et si l'obj n'est pas de type EntityBase, il lèvera InvalidCastException. Et oui, "as" ne peut être appliqué qu'aux types de référence.
ça. __curious_geek
1
Le lien de Josh vers le blog de Jared Par semble suggérer que vous devez également remplacer GetHashCode. Ce n'est pas le cas?
Amiable le
3
Je n'ai pas vraiment la valeur supplémentaire que votre implémentation offre. Pouvez-vous clarifier le problème que votre classe de base abstraite résout?
Mert Akcakaya
1
@Amicable - oui, chaque fois que vous remplacez Object.Equals (Object), vous devez également remplacer GetHashCode pour que les conteneurs fonctionnent.
namford
0

Si nous appelons object.Equals, cela oblige à une boxe coûteuse sur les types de valeur. Cela n'est pas souhaitable dans les scénarios sensibles aux performances. La solution est d'utiliser IEquatable<T>.

public interface IEquatable<T>
{
  bool Equals (T other);
}

L'idée derrière IEquatable<T>est que cela donne le même résultat que object.Equalsmais plus rapidement. La contrainte where T : IEquatable<T>doit être utilisée avec des types génériques comme ci-dessous.

public class Test<T> where T : IEquatable<T>
{
  public bool IsEqual (T a, T b)
  {
    return a.Equals (b); // No boxing with generic T
  }
}

sinon, il se lie à slower object.Equals().

Seyedraouf Modarresi
la source