Joan Venge
Joan Venge

Reputation: 331082

How to best implement Equals for custom types?

Say for a Point2 class, and the following Equals:

public override bool Equals ( object obj )

public bool Equals ( Point2 obj )

This is the one that is shown in the 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 ) ) );
}

Upvotes: 50

Views: 75354

Answers (11)

I've found this to work for me - I let GetHashCode() to do all the heavy lifting.

After that, any two objects are equal if and only if their hash codes are equal.

    public override int GetHashCode() => <-- IMPLEMENT THIS -->
    public virtual bool Equals(T other) => other?.GetHashCode() == GetHashCode();
    public override bool Equals(object other) => Equals(other as T);
    public static bool operator ==(T item1, T item2) => item1?.GetHashCode() == item2?.GetHashCode();
    public static bool operator !=(T item1, Titem2) => !(item1 == item2);

Example of a Wrapper class which adds some info to a sealed class of type S.

public class Wrapper<S>
{
    public S obj;

    // Handle obj being null
    public override int GetHashCode() => obj?.GetHashCode() ?? 0;

    // The rest are the same as above
    public virtual bool Equals(Wrapper<S> other) => other?.GetHashCode() == GetHashCode();
    public override bool Equals(object other) => Equals(other as Wrapper<S>);
    public static bool operator ==(Wrapper<S> item1, Wrapper<S> item2) => item1?.GetHashCode() == item2?.GetHashCode();
    public static bool operator !=(Wrapper<S> item1, Wrapper<S> item2) => !(item1 == item2);
}

Upvotes: 1

Brad Bodily
Brad Bodily

Reputation: 51

Using C# 7 and the is type varname pattern matching syntax provides for a clean Equals(object) that deals with null and type checking using either of the below approaches:

// using strongly-typed overload of Equals
public override bool Equals(object obj) => (obj is Point2 other) && Equals(other);

public bool Equals(Point2 other);
// using the == operator (requires != to also be defined)
public override bool Equals(object obj) => (obj is Point2 other) && this == other;

public static bool operator ==(Point2 lhs, Point2 rhs);
public static bool operator !=(Point2 lhs, Point2 rhs);

(Obviously you need to provide implementation of the relevant method stubs.)

Upvotes: 5

Henk Holterman
Henk Holterman

Reputation: 273274

There is a whole set of guidelines on MSDN as well. You should read them well, it is both tricky and important.

A few points I found most helpful:

  • Value Types don't have Identity, so in a struct Point you will usually do a member by member compare.

  • Reference Types usually do have identity, and therefore the Equals test usually stops at ReferenceEquals (the default, no need to override). But there are exceptions, like string and your class Point2, where an object has no useful identity and then you override the Equality members to provide your own semantics. In that situation, follow the guidelines to get through the null and other-type cases first.

  • And there are good reasons to keep GethashCode() and operator== in sync as well.

Upvotes: 36

LIDEN
LIDEN

Reputation: 166

The simple and best way to override Equals looks like:

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

Override the GetHashCode method to allow a type to work correctly in a hash table.

Upvotes: 2

mamuesstack
mamuesstack

Reputation: 1251

There is also a Fody plugin Equals.Fody that generates Equals() and GetHashCode() automatically

Upvotes: 2

Steazy
Steazy

Reputation: 393

Slight variants of forms already posted by several others...

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

Trying to find a way to implement operator == using Equals to avoid duplicating the value comparison logic... without any redundant tests (ReferenceEquals calls w/ the same parameters) or unnecessary tests (this can't be null in the instance.Equals method) and without any explicit conditionals ("ifs"). More of a mind teaser than anything useful.

Closest I can think of is this, but it feels like it should be possible without an extra method :)

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

Remembering how tedious and error prone this all is (I'm almost sure there's an error in the above code... which still sucks because who wants to subclass a Type just to make equality checks slightly simpler?), going forward I think I'll just create some static methods that handle all the null checks and accept a delegate or require and interface to perform the comparison of values (the only part that really changes Type to Type).

It'd be great if we could just add attributes onto the fields/properties/methods that need to be compared and let the compiler/runtime handle all the tedium.

Also make sure GetHashCode() values are equal for any instances in which which .Equals(object) returns true or crazy shit can happen.

Upvotes: 0

user1325543
user1325543

Reputation: 531

The technique I've used that has worked for me is as follows. Note, I'm only comparing based on a single property (Id) rather that two values. Adjust as needed

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

Upvotes: 15

Daniel LeCheminant
Daniel LeCheminant

Reputation: 51081

In the one that takes an obj, if the type of obj is Point2, call the type specific Equals. Inside the type specific Equals, make sure that all the members have the same value.

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.
}

You probably ought to override GetHashCode as well to make sure that objects that are "equal" have the same hash code.

Upvotes: 47

baretta
baretta

Reputation: 7595

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 )

Upvotes: 0

configurator
configurator

Reputation: 41630

Lie Daniel L said,

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) {
    ...
}

Upvotes: 0

Richard
Richard

Reputation: 109025

  • Define what the identity means.. if reference identity then the default inherited equals will work.
  • If a value type (and thus value identity) you need to define.
  • If a class type, but has value semantics then define.

Likely you want to both override Equals(object) and define Equals(MyType) because the latter avoids boxing. And override the equality operator.

.NET Framework Guidelines book (2nd ed) has more coverage.

Upvotes: 2

Related Questions