46 votes

Existe-t-il une référence complète de l'implémentation de IEquatable ?

La plupart des questions que je pose ici sur SO concernent l'implémentation de IEquatable. J'ai trouvé qu'il était extrêmement difficile de l'implémenter correctement, parce qu'il y a beaucoup de bugs cachés dans l'implémentation naïve, et les articles que j'ai trouvés à ce sujet sont assez incomplets. Je veux trouver ou écrire une référence définitive qui doit inclure :

  • Comment implémenter correctement IEquatable
  • Comment remplacer correctement les égalités
  • Comment remplacer correctement GetHashCode ?
  • Comment implémenter correctement la méthode ToString ?
  • Comment implémenter correctement l'opérateur == ?
  • Comment implémenter correctement l'opérateur != ?

Une telle référence complète existe déjà ?

PS : Même Référence MSDN me semble défectueux

23voto

user7116 Points 39829

Mise en œuvre IEquatable<T> pour un type de valeur

Mise en œuvre IEquatable<T> pour un type de valeur est un peu différente de celle d'un type de référence. Supposons que nous ayons l'archétype Implement-Your-Own-Value-Type, un Complex number struct.

public struct Complex
{
    public double RealPart { get; set; }
    public double ImaginaryPart { get; set; }
}

La première étape consisterait à mettre en œuvre IEquatable<T> et de passer outre Object.Equals y Object.GetHashCode :

public bool Equals(Complex other)
{
    // Complex is a value type, thus we don't have to check for null
    // if (other == null) return false;

    return (this.RealPart == other.RealPart)
        && (this.ImaginaryPart == other.ImaginaryPart);
}

public override bool Equals(object other)
{
    // other could be a reference type, the is operator will return false if null
    if (other is Complex)
        return this.Equals((Complex)other);
    else
        return false;
}

public override int GetHashCode()
{
    return this.RealPart.GetHashCode() ^ this.ImaginaryPart.GetHashCode();
}

Avec très peu d'efforts, nous avons une mise en œuvre correcte, à l'exception des opérateurs. L'ajout des opérateurs est également un processus trivial :

public static bool operator ==(Complex term1, Complex term2)
{
    return term1.Equals(term2);
}

public static bool operator !=(Complex term1, Complex term2)
{
    return !term1.Equals(term2);
}

Un lecteur avisé remarquera que nous devrions probablement mettre en place IEquatable<double> depuis Complex pourraient être interchangeables avec le type de valeur sous-jacent.

public bool Equals(double otherReal)
{
    return (this.RealPart == otherReal) && (this.ImaginaryPart == 0.0);
}

public override bool Equals(object other)
{
    // other could be a reference type, thus we check for null
    if (other == null) return base.Equals(other);

    if (other is Complex)
    {
        return this.Equals((Complex)other);
    }
    else if (other is double)
    {
        return this.Equals((double)other);
    }
    else
    {
        return false;
    }
}

Nous avons besoin de quatre opérateurs si nous ajoutons IEquatable<double> car vous pouvez avoir Complex == double o double == Complex (et de même pour les operator != ) :

public static bool operator ==(Complex term1, double term2)
{
    return term1.Equals(term2);
}

public static bool operator ==(double term1, Complex term2)
{
    return term2.Equals(term1);
}

public static bool operator !=(Complex term1, double term2)
{
    return !term1.Equals(term2);
}

public static bool operator !=(double term1, Complex term2)
{
    return !term2.Equals(term1);
}

Voilà, avec un minimum d'efforts, nous avons une mise en œuvre correcte et utile. IEquatable<T> pour un type de valeur :

public struct Complex : IEquatable<Complex>, IEquatable<double>
{
}

14voto

Jader Dias Points 23461

J'ai finalement écrit un exemple de code qui montre la bonne façon d'implémenter toutes les méthodes liées à IEquatable :

http://code.google.com/p/iequatable-implementation-reference/source/browse/IEquatableReference/trunk/IEquatableReference/ValuesClass.cs

Les tests unitaires qui justifient chaque ligne de code sont là :

http://code.google.com/p/iequatable-implementation-reference/source/browse/IEquatableReference/trunk/IEquatableReferenceTest/ValuesClassTest.cs

C'est juste que je n'ai pas écrit de test unitaire pour le dépassement arithmétique de GetHashCode.

11voto

nawfal Points 13500

Je pense que la conception de .NET rend un exercice aussi simple que la vérification de l'égalité des objets un peu difficile.

Pour Struct

1) Mise en œuvre IEquatable<T> . Il améliore sensiblement les performances.

2) Puisque vous avez votre propre Equals maintenant, passer outre GetHashCode et pour être cohérent avec les différents contrôles de l'égalité, il convient d'ignorer les règles de l'Union européenne. object.Equals également.

3) Surcharge == y != n'ont pas besoin d'être religieusement utilisés puisque le compilateur vous avertira si vous assimilez involontairement une structure à une autre avec un opérateur == o != mais il est bon de le faire pour être cohérent avec les règles de l'Union européenne. Equals des méthodes.

public struct Entity : IEquatable<Entity>
{
    public bool Equals(Entity other)
    {
        throw new NotImplementedException("Your equality check here...");
    }

    public override bool Equals(object obj)
    {
        if (obj == null || !(obj is Entity))
            return false;

        return Equals((Entity)obj);
    }

    public static bool operator ==(Entity e1, Entity e2)
    {
        return e1.Equals(e2);
    }

    public static bool operator !=(Entity e1, Entity e2)
    {
        return !(e1 == e2);
    }

    public override int GetHashCode()
    {
        throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
    }
}

Pour la classe

De MS :

La plupart des types de référence ne doivent pas surcharger l'opérateur d'égalité, même s'ils surchargent Equals.

Pour moi == ressemble à une égalité de valeur, plus qu'à un sucre syntaxique pour les Equals méthode. L'écriture a == b est beaucoup plus intuitif que d'écrire a.Equals(b) . Il est rare que nous devions vérifier l'égalité des références. Dans les niveaux abstraits traitant des représentations logiques d'objets physiques, ce n'est pas quelque chose que nous devrions vérifier. Je pense que le fait d'avoir une sémantique différente pour == y Equals peut en fait être source de confusion. Je pense qu'il aurait fallu == pour l'égalité des valeurs et Equals pour référence (ou un meilleur nom comme IsSameAs ) en premier lieu. J'aimerais ne pas prendre au sérieux la ligne directrice sur les EM, non seulement parce qu'elle n'est pas naturelle pour moi, mais aussi parce qu'elle est surchargée == n'a pas d'effet néfaste majeur. C'est la différence avec le fait de ne pas passer par-dessus des Equals o GetHashCode qui peut mordre, parce que le cadre n'utilise pas de == n'importe où, mais seulement si nous l'utilisons nous-mêmes. Le seul véritable avantage que je retire de ne pas surcharger == y != sera la cohérence avec la conception de l'ensemble du cadre, sur laquelle je n'ai aucun contrôle. Et c'est effectivement une chose importante, Je m'en tiendrai donc malheureusement à cela .

Avec une sémantique de référence (objets mutables)

1) Annulation Equals y GetHashCode .

2) Mise en œuvre IEquatable<T> n'est pas indispensable, mais sera utile si vous en avez un.

public class Entity : IEquatable<Entity>
{
    public bool Equals(Entity other)
    {
        if (ReferenceEquals(this, other))
            return true;

        if (ReferenceEquals(null, other))
            return false;

        //if your below implementation will involve objects of derived classes, then do a 
        //GetType == other.GetType comparison
        throw new NotImplementedException("Your equality check here...");
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as Entity);
    }

    public override int GetHashCode()
    {
        throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
    }
}

Avec une sémantique de valeur (objets immuables)

C'est la partie la plus délicate. Elle peut être facilement perturbée si l'on n'y prend pas garde

1) Annulation Equals y GetHashCode .

2) Surcharge == y != pour correspondre Equals . S'assurer qu'il fonctionne pour les nullités .

2) Mise en œuvre IEquatable<T> n'est pas indispensable, mais sera utile si vous en avez un.

public class Entity : IEquatable<Entity>
{
    public bool Equals(Entity other)
    {
        if (ReferenceEquals(this, other))
            return true;

        if (ReferenceEquals(null, other))
            return false;

        //if your below implementation will involve objects of derived classes, then do a 
        //GetType == other.GetType comparison
        throw new NotImplementedException("Your equality check here...");
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as Entity);
    }

    public static bool operator ==(Entity e1, Entity e2)
    {
        if (ReferenceEquals(e1, null))
            return ReferenceEquals(e2, null);

        return e1.Equals(e2);
    }

    public static bool operator !=(Entity e1, Entity e2)
    {
        return !(e1 == e2);
    }

    public override int GetHashCode()
    {
        throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
    }
}

Faites particulièrement attention à ce qu'il se passe si votre classe peut être héritée, dans ce cas vous devrez déterminer si un objet de la classe de base peut être égal à un objet de la classe dérivée. Idéalement, si aucun objet de la classe dérivée n'est utilisé pour vérifier l'égalité, alors une instance de la classe de base peut être égale à une instance de la classe dérivée et, dans ce cas, il n'est pas nécessaire de vérifier la conformité des objets de la classe dérivée. Type l'égalité dans les produits génériques Equals de la classe de base.

En général, il faut veiller à ne pas dupliquer le code. J'aurais pu créer une classe de base abstraite générique ( IEqualizable<T> ) comme modèle pour faciliter la réutilisation, mais malheureusement, en C#, cela m'empêche de dériver à partir de classes supplémentaires.

4voto

user7116 Points 39829

En lisant MSDN, je suis presque certain que le meilleur exemple d'une mise en œuvre correcte se trouve dans la section Méthode IEquatable.Equals page. Ma seule divergence est la suivante :

public override bool Equals(Object obj)
{
   if (obj == null) return base.Equals(obj);

   if (! (obj is Person))
      return false; // Instead of throw new InvalidOperationException
   else
      return Equals(obj as Person);   
}

Pour ceux qui s'interrogent sur cette déviation, elle provient de l'expression Object.Equals(Object) Page MSDN :

Les implémentations de Equals ne doivent pas lever d'exceptions.

4voto

Jader Dias Points 23461

J'ai trouvé une autre référence, il s'agit de l'implémentation des types anonymes .NET. Pour un type anonyme avec un int et un double comme propriétés, j'ai désassemblé le code C# suivant :

public class f__AnonymousType0
{
    // Fields
    public int A { get; }
    public double B { get; }

    // Methods
    public override bool Equals(object value)
    {
        var type = value as f__AnonymousType0;
        return (((type != null)
            && EqualityComparer<int>.Default.Equals(this.A, type.A))
            && EqualityComparer<double>.Default.Equals(this.B, type.B));
    }

    public override int GetHashCode()
    {
        int num = -1134271262;
        num = (-1521134295 * num) + EqualityComparer<int>.Default.GetHashCode(this.A);
        return ((-1521134295 * num) + EqualityComparer<double>.Default.GetHashCode(this.B);
    }

    public override string ToString()
    {
        StringBuilder builder = new StringBuilder();
        builder.Append("{ A = ");
        builder.Append(this.A);
        builder.Append(", B = ");
        builder.Append(this.B);
        builder.Append(" }");
        return builder.ToString();
    }
}

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