Failure to check for type can lead to un-symmetric equality:
public sealed class MyClass : Tuple<string>
{
private readonly int _b;
public MyClass(string a, int b) : base(a)
{
_b = b;
}
public override bool Equals(object obj)
{
return Equals(obj as MyClass);
}
private bool Equals(MyClass obj)
{
if (obj == null) return false;
return base.Equals(obj) && obj._b == _b;
}
}
[Test]
public void Show_broken_symmetric_equality()
{
Tuple<string> a = Tuple.Create("Test");
var b = new MyClass("Test", 3);
Assert.AreEqual(a, b);
Assert.AreNotEqual(b, a);
}
That test passes, but it should not, it shows the symmetric property of a well implemented Equals
is broken.
Looking at the code for Tuple
that's because Tuple doesn't check the concrete types match, i.e. there is no equivalent of GetType() == obj.GetType()
. It checks assignability, with an is
check, but doesn't compare types.
There's nothing I can do in MyClass
to fix this situation, because the incorrect line is Assert.AreEqual(a, b);
which is the call to Tuple.Equals
. And, as juharr points out, changing MyClass.Equals
to return true in this case would break transitivity.
Long shot, but I wonder if anyone knows a good reason why it has been implemented in such a way? Or why it was not sealed if it was implemented this way.
No, I have looked at this before and as far as I understand there is no good reason why they don't check the type properly (except for that they got it wrong from the start and then it's of course impossible to change it).
Every MSDN advice on best practice for equals talks about doing "GetType() != obj.GetType()" to make sure the types are exactly the same, but equals in Tuple only does a cast with 'as' operator which will (as you noticed) give unexpected result and disable the ability for derived classes to adhere to best practices for equals.
IMHO - Don't derive from Tuple, and definitely don't implement equals if you do it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With