Search code examples
c#equality

Equals implementation with override Equals, IEquatable, == and !=


I am seeing many posts and questions about equality in C# and how to implement it. I wanted to try to collect the best practices and provide an solution with more or less no boilerplate code where no code is repeated.

Starting of with a simple class like that:

public class Foo
{
    public int Bar { get; set; }
}

I want now to add to this class every possibility for an Equals call starting with overriding it with ReSharper recommendations:

public override bool Equals(object obj)
{
    if (ReferenceEquals(null, obj)) return false;
    if (ReferenceEquals(this, obj)) return true;
    if (obj.GetType() != GetType()) return false;

    if(obj is Foo foo)
    {
        // here all the values should be checked
        return Bar == foo.Bar;
    }

    return false;
}

Now I have overriden the Equals call but are left with the == and != operators with that recommendation https://www.loganfranken.com/blog/698/overriding-equals-in-c-part-3/

public static bool operator ==(Foo a, 
                               Foo b)
{
    if(ReferenceEquals(null, a)) return false; 
            
    return a.Equals(b); 
}

public static bool operator !=(Foo a,
                               Foo b)
{
    return !(a == b);
}

The last step now would be to implement the IEquatable<> interface like this and adjusting the override Equals leaving the class like this:

public class Foo : IEquatable<Foo>
{
    public int Bar { get; set; }

    public bool Equals(Foo other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;

        // here all the values should be checked
        return Bar == other.Bar;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (obj.GetType() != GetType()) return false;

        return Equals(obj as Foo);
    }

    public static bool operator ==(Foo a,
                                   Foo b)
    {
        if(ReferenceEquals(null, a)) return false;
        
        return a.Equals(b);
    }

    public static bool operator !=(Foo a,
                                   Foo b)
    {
        return !(a == b);
    }
}

Now in my eyes I have removed more or less all duplicate code (unfortunately I cannot think of a way to get rid of the null-checks). I would implement the "core-logic" in the Equals(Foo other) method and the others could be copied to any other classes.

is there any pitfall or problem I am missing here? Or any recommendation on getting lesser code? I am not trying to get the best performance, clean code should be the main criteria in this implementation and reusability for other classes. So would it be possible to copy the last 3 methods without problems to any other class and only implementing the IEquatable<> there and changing the call of course?

Also note that I did not implement GetHashCode() right now.


Solution

  • You should not repeat yourself: implementing public bool Equals(Foo other) is enough; all the other: Equals, ==, != can be derived from Equals(Foo other):

    public class Foo : IEquatable<Foo>
    {
        public int Bar { get; set; }
    
        public bool Equals(Foo other)
        {
            if (ReferenceEquals(this, other)) return true;
     
            if (other is null) return false;
    
            // here all the values should be checked
            return Bar == other.Bar;
        }
    
        // Here we can put it short 
        public override bool Equals(object obj) => obj is Foo other && Equals(other);
    
        //TODO: Don't forget about hash 
        // Here I put the easiest approach which exploits the fact we
        // have just one property of type int. 
        public override int GetHashCode() => Bar;
    
        // Note ReferenceEquals(a, b): we want null == null be true
        public static bool operator == (Foo a, Foo b) =>
          ReferenceEquals(a, b) || (a is not null && a.Equals(b));
    
        public static bool operator !=(Foo a, Foo b) => !(a == b);
    }