web-dev-qa-db-fra.com

Comment implémenter au mieux Equals pour les types personnalisés?

Dites pour une classe Point2, et les égaux suivants:

public override bool Equals ( object obj )

public bool Equals ( Point2 obj )

C'est celui qui est montré dans le Effective C # 3:

public override bool Equals ( object obj )
{
    // STEP 1: Check for null
    if ( obj == null )
    {
        return false;
    }

    // STEP 3: equivalent data types
    if ( this.GetType ( ) != obj.GetType ( ) )
    {
        return false;
    }
    return Equals ( ( Point2 ) obj );
}

public bool Equals ( Point2 obj )
{
    // STEP 1: Check for null if nullable (e.g., a reference type)
    if ( obj == null )
    {
        return false;
    }
    // STEP 2: Check for ReferenceEquals if this is a reference type
    if ( ReferenceEquals ( this, obj ) )
    {
        return true;
    }
    // STEP 4: Possibly check for equivalent hash codes
    if ( this.GetHashCode ( ) != obj.GetHashCode ( ) )
    {
        return false;
    }
    // STEP 5: Check base.Equals if base overrides Equals()
    System.Diagnostics.Debug.Assert (
        base.GetType ( ) != typeof ( object ) );

    if ( !base.Equals ( obj ) )
    {
        return false;
    }

    // STEP 6: Compare identifying fields for equality.
    return ( ( this.X.Equals ( obj.X ) ) && ( this.Y.Equals ( obj.Y ) ) );
}
30
Joan Venge

Il existe également un ensemble complet de instructions sur MSDN . Vous devriez bien les lire, c'est à la fois délicat et important. 

Quelques points que j'ai trouvés les plus utiles:

  • Les types de valeur n'ont pas d'identité. Par conséquent, dans un struct Point, vous effectuerez généralement une comparaison membre par membre.

  • Les types de référence ont généralement une identité et, par conséquent, le test Equals s'arrête généralement à ReferenceEquals (valeur par défaut, il n'est pas nécessaire de remplacer). Mais il existe des exceptions, telles que string et votre class Point2, lorsqu'un objet n'a aucune identité utile et que vous substituez ensuite les membres Equality pour fournir votre propre sémantique. Dans ce cas, suivez les instructions pour résoudre d'abord les cas null et other-type.

  • Et il y a de bonnes raisons de garder GethashCode() et operator== également synchronisés. 

22
Henk Holterman

Dans celui qui prend un obj, si le type d'obj est Point2, appelez le type spécifique Equals. Dans le type spécifique Equals, assurez-vous que tous les membres ont la même valeur.

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

public bool Equals ( Point2 obj )
{
   return obj != null && obj.X == this.X && obj.Y == this.Y ... 
   // Or whatever you think qualifies as the objects being equal.
}

Vous devez probablement également redéfinir GetHashCode pour vous assurer que les objets qui sont "égaux" ont le même code de hachage.

34
Daniel LeCheminant

La technique que j'ai utilisée et qui a fonctionné pour moi est la suivante. Notez que je compare uniquement en fonction d'une propriété unique (Id) plutôt que de deux valeurs. Ajustez au besoin 

using System;
namespace MyNameSpace
{
    public class DomainEntity
    {
        public virtual int Id { get; set; }

        public override bool Equals(object other)
        {
            return Equals(other as DomainEntity);
        }

        public virtual bool Equals(DomainEntity other)
        {
            if (other == null) { return false; }
            if (object.ReferenceEquals(this, other)) { return true; }
            return this.Id == other.Id;
        }

        public override int GetHashCode()
        {
            return this.Id;
        }

        public static bool operator ==(DomainEntity item1, DomainEntity item2)
        {
            if (object.ReferenceEquals(item1, item2)) { return true; }
            if ((object)item1 == null || (object)item2 == null) { return false; }
            return item1.Id == item2.Id;
        }

        public static bool operator !=(DomainEntity item1, DomainEntity item2)
        {
            return !(item1 == item2);
        }
    }
}
8
user1325543
  • Définissez ce que l’identité signifie. Si l’identité de référence est définie, l’égalité héritée par défaut fonctionnera.
  • Si un type de valeur (et donc une identité de valeur) vous devez définir.
  • Si un type de classe a une sémantique de valeur, définissez-le.

Probablement vous voulez les deux / remplacer Equals (objet) et définir Equals (MyType) car ce dernier évite la boxe. Et remplacez l'opérateur d'égalité.

Le livre .NET Framework Guidelines (2nd ed) couvre davantage.

2
Richard

Lie Daniel L a dit:

public override bool Equals(object obj) {
    Point2 point = obj as Point2; // Point2? if Point2 is a struct
    return point != null && this.Equals(point);
}

public bool Equals(Point2 point) {
    ...
}
0
configurator
public override bool Equals ( object obj )
{
   // struct
   return obj  is Point2 && Equals (  ( Point2 ) value );
   // class
   //return Equals ( obj as Point2 );
}

public bool Equals ( Point2 obj )
0
baretta

Il existe également un plugin Fody Equals.Fody qui génère automatiquement Equals () et GetHashCode ()

0
mamuesstack

Légères variantes de formulaires déjà postés par plusieurs autres ...

using System;
...
public override bool Equals ( object obj ) {
   return Equals(obj as SomeClass);
}

public bool Equals ( SomeClass someInstance ) {
    return Object.ReferenceEquals( this, someInstance ) 
        || ( !Object.ReferenceEquals( someInstance, null ) 
            && this.Value == someInstance.Value );
}

public static bool operator ==( SomeClass lhs, SomeClass rhs ) {
    if( Object.ReferenceEquals( lhs, null ) ) {
        return Object.ReferenceEquals( rhs, null );
    }
    return lhs.Equals( rhs );
    //OR
    return Object.ReferenceEquals( lhs, rhs )
            || ( !Object.ReferenceEquals( lhs, null ) 
                && !Object.ReferenceEquals( rhs, null )
                && lhs.Value == rhs.Value );
}

public static bool operator !=( SomeClass lhs, SomeClass rhs ) {
    return !( lhs == rhs );
    // OR
    return ( Object.ReferenceEquals( lhs, null ) || !lhs.Equals( rhs ) )
            && !Object.ReferenceEquals( lhs, rhs );
}

Essayer de trouver un moyen d'implémenter l'opérateur == en utilisant Equals pour éviter de dupliquer la logique de comparaison de valeurs ... sans tests redondants (appels ReferenceEquals avec les mêmes paramètres) ni tests inutiles (cela ne peut pas être nul dans l'instance.Equals méthode) et sans aucune condition explicite ("ifs"). Plus un casse-tête que tout ce qui est utile.

Le plus proche auquel je puisse penser est celui-ci, mais il pense comme si cela devait être possible sans méthode supplémentaire :)

public bool Equals ( SomeClass someInstance ) {
    return Object.ReferenceEquals( this, someInstance ) 
        || (!Object.ReferenceEquals( someInstance, null ) && EqualsNonNullInstance( someInstance );
}

public static bool operator ==( SomeClass lhs, SomeClass rhs ) {
    return Object.ReferenceEquals( lhs, rhs ) 
    || ( !Object.ReferenceEquals( lhs, null ) && !Object.ReferenceEquals( rhs, null ) && lhs.EqualsNonNullInstance( rhs ) );
}

//super fragile method which returns logical non-sense
protected virtual bool EqualsNonNullInstance ( SomeClass someInstance ) {
    //In practice this would be a more complex method...
    return this.Value == someInstance.Value;
}

Rappelez-vous à quel point tout cela est fastidieux et sujet aux erreurs (je suis presque sûr qu'il y a une erreur dans le code ci-dessus ... qui craint toujours car qui veut sous-classer un Type juste pour rendre les contrôles d'égalité légèrement plus simples?) forward Je pense que je vais simplement créer des méthodes statiques qui gèrent toutes les vérifications nulles et accepter un délégué ou une option et une interface pour effectuer la comparaison de valeurs (la seule partie qui modifie réellement Type en Type).

Ce serait formidable si nous pouvions simplement ajouter des attributs aux champs/propriétés/méthodes qui doivent être comparés et laisser le compilateur/le moteur d’exécution gérer tout l’ennui.

Assurez-vous également que les valeurs de GetHashCode () sont égales pour toutes les instances dans lesquelles .Equals (object) renvoie true ou crazy shit peut se produire.

0
Steazy

Le moyen le plus simple et le plus pratique de remplacer Equals est le suivant:

public class Person
    {
        public int Age { get; set; }
        public string Name { get; set; }

        public override bool Equals(object other)
        {
            Person otherItem = other as Person;

            if (otherItem == null)
                return false;

            return Age == otherItem.Age && Name == otherItem.Name;
        }
        public override int GetHashCode()
        {
            int hash = 13;
            hash = (hash * 7) + Age.GetHashCode();
            hash = (hash * 7) + Name.GetHashCode();
            return hash;
        }
    }

Remplacez la méthode GetHashCode pour permettre à un type de fonctionner correctement dans une table de hachage. 

0
LIDEN