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;
