Any good reason why Tuple.Equals is not checking for exact types?

Failure to check type can lead to asymmetric 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); } 

This test passes, but it should not, it shows that the symmetric property of the well-implemented Equals broken.

Considering the Tuple code, because Tuple does not check for specific types, i.e. there is no equivalent to GetType() == obj.GetType() . It checks assignment by checking is , but does not compare types.

I can not do anything in MyClass to correct this situation, because the wrong line is Assert.AreEqual(a, b); , which is a call to Tuple.Equals . And, as yukharr points out, modifying MyClass.Equals to return true will violate transitivity in this case.

A long shot, but I wonder if anyone knows why it was implemented this way? Or why it was not sealed if it was implemented in this way.

+6
source share
3 answers

No, I looked at this earlier, and as far as I understand, there are no good reasons why they do not check the type properly (except that they were wrong from the very beginning, and then, of course, it is impossible to change it).

Each MSDN peer good practice tip talks about doing "GetType ()! = Obj.GetType ()" to make sure the types are exactly the same but equal in Tuple, only doing the action with an "like" operator that will ( as you noticed) give an unexpected result and disable the ability of derived classes to adhere to best practices for equals.

IMHO - does not work from Tuple and certainly does not implement equals if you do this.

+2
source

I implemented an alternative that checks the type and, as far as I am sure, implements the equality contract correctly:

https://mercurynuget.imtqy.com/SuperTuples/

To create a class with the implementation of GetHashcode and Equals (plus a ToString as well as Tuple), the hash is optionally cached.

 public class Person : Suple<string, string> { public Person(string firstName, string lastName) : base(firstName, lastName, SupleHash.Cached) { } public string FirstName => Item1; public string LastName => Item2; } 

The implementation is simpler than Tuple , so you can make more boxing during Equals , but in practice it performs Tuple Equals even 8 times when the hash is cached ( SupleHash.Cached ) and SupleHash.Cached caching minimizes boxing and other Equals calls, since Equals first compares the cached hash.

Available for nuget:

Install-Package SuperTuples

As an example of how this works, this is the code for triplex:

 public abstract class Suple<T1, T2, T3> { private readonly T1 _item1; private readonly T2 _item2; private readonly T3 _item3; private readonly int? _cachedHash; protected Suple(T1 item1, T2 item2, T3 item3) { _item1 = item1; _item2 = item2; _item3 = item3; } protected Suple(T1 item1, T2 item2, T3 item3, SupleHash hashMode) { _item1 = item1; _item2 = item2; _item3 = item3; _cachedHash = CalculateHashCode(); } protected T1 Item1 { get { return _item1; } } protected T2 Item2 { get { return _item2; } } protected T3 Item3 { get { return _item3; } } public override bool Equals(object obj) { if (obj == null) return false; // here the missing Tuple type comparison if (GetType() != obj.GetType()) return false; var other = (Suple<T1, T2, T3>) obj; // attempt to avoid equals comparison by using the // cached hash if provided by both objects if (_cachedHash != null && other._cachedHash != null && _cachedHash != other._cachedHash) return false; return Equals(_item1, other._item1) && Equals(_item2, other._item2) && Equals(_item3, other._item3); } public override int GetHashCode() { return _cachedHash ?? CalculateHashCode(); } private int CalculateHashCode() { unchecked { int hashcode = 0; hashcode += _item1 != null ? _item1.GetHashCode() : 0; hashcode *= 31; hashcode += _item2 != null ? _item2.GetHashCode() : 0; hashcode *= 31; hashcode += _item3 != null ? _item3.GetHashCode() : 0; return hashcode; } } } 
+1
source

See the source of the Tuple class (line 100) http://referencesource.microsoft.com/#mscorlib/system/tuple.cs,2e0df2b1d6d668a0

Evaluation order for operators from left to right

-4
source

All Articles