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;