Home > Software engineering >  Overloading operators *only* for the base class
Overloading operators *only* for the base class

Time:10-20

In C#, I have a base class Hex where I'm overloading its equality operators like so:

public class Hex {
    #region Equality

    public static bool operator ==(Hex a, Hex b) {
        if (a is null) { return b is null; }
        if (b is null) { return false; }
        // Two hex instances are equal if their coordinates match
        return a.Q == b.Q && a.R == b.R && a.S == b.S;
    }
    public static bool operator !=(Hex a, Hex b) { return !(a == b); }
    public override bool Equals(object obj) => obj is Hex x && this == x;

    #endregion
}

I extend the class to hold some extra info:

public class HexWithMeta : Hex {
    public int OwnerId { get; set; }
}

I then expected the following method to return false, yet it returns true:

public bool DoTest() {
    var h1 = new HexWithMeta(1, 1, -2);
    var h2 = new HexWithMeta(1, 1, -2);
    return h1 == h2;
}

This seems to be because h1 == h2 is calling the overloaded == operator on the Hex class. However, I'm comparing HexWithMeta objects, not Hex objects. How can I get == to compare referential equality for classes like HexWithMeta that extend Hex, but use the custom operator code for comparing the base Hex class instances?

CodePudding user response:

A standard implementation of Equality (as generated by R#) should look like this:

public class MyClass : IEquatable<MyClass>
{
    private int myProperty;
    public static bool operator ==(MyClass left, MyClass right) => Equals(left, right);
    public static bool operator !=(MyClass left, MyClass right) => !Equals(left, right);
    public bool Equals(MyClass other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return myProperty == other.myProperty;
    }
    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((MyClass)obj);
    }
    public override int GetHashCode() => myProperty;
}

Notably the obj.GetType() != this.GetType() that checks that the actual types match.

Note that you should also override the equality members for HexWithMeta, because not doing so would be terribly confusing. And I really do not understand why you expect your example not to compare true, if you create two objects with the same parameters I would very much expect them to compare identical.

If you want to do anything fancy with comparison I would highly recommend instead creating an implementation of the IEqualityComparer<T> interface. This should also be the interface you should accept whenever you compare any type of generic object. Use EqualityComparer<T>.Default to get a default implementation for a generic type.

CodePudding user response:

Implement the entire comparison logic including the compare-by-reference logic for types deriving from Hex in the Hex operator overloads. For example, the == operator in Hex could look like this:

public static bool operator ==(Hex a, Hex b)
    => (a is null) ? b is null
        : (b is null) ? false
        // Equality-by-reference for any instances of types derived from Hex
        : (a.GetType() != typeof(Hex) || b.GetType() != typeof(Hex)) ? object.ReferenceEquals(a, b)
        // Two instances of type Hex are equal if their coordinates match
        : a.Q == b.Q && a.R == b.R && a.S == b.S;
  • Related