30

Until today, my understanding of .NET Tuple classes had been that they delegate their implementation of Equals() to their contents, allowing me to equate and compare them "by value".

Then this test came along and made a fool out of me:

[TestMethod]
public void EquateTwoTuplesWithSameContent()
{
    var t1 = Tuple.Create("S");
    var t2 = Tuple.Create((object)t1.Item1);
    Assert.IsTrue(t1.Equals(t2)); // Boom!
}

Reading through MSDN documentation and various blogs has left me with more questions. From what I gather, it would seem that Tuple<object> and Tuple<TWhatever> are always considered not equal, regardless of the fact that both instances may wrap the same object (boxed or typecast - it's all the same).

Is this really how Tuples are supposed to behave? Is structural compatibility actually an additional constraint on equality as opposed to a relaxation, as I've been interpreting it until now?

If so, is there anything else in the BCL that I can use to meet the expectations of the above unit test?

Thank you in advance!

aoven
  • 2,058
  • 2
  • 21
  • 33

3 Answers3

54

Tuples require the following to be true for the objects to be considered "equal":

  • Must be a Tuple object with the same number of generic parameter(s) as the current object.
  • Each of those generic parameters must be of the same type as the other.
  • Each member of the tuple must have the same value as the corresponding member of the other.

So, because a Tuple<object> has a different generic parameter than a Tuple<string>, they are not equal even if the object is actually a reference to a string of the same value as the strongly-typed Tuple<string>.

KeithS
  • 65,745
  • 16
  • 102
  • 161
  • So, one way to think about it is "there is no covariance in Type parameters", in general? Or is this phenomenon unique to `Tuple` types? – radarbob Jul 18 '13 at 15:03
  • 2
    Ah. [This SO question](http://stackoverflow.com/questions/2872867/is-c-sharp-4-0-tuple-covariant) answers my question. "... only interfaces and delegate types support generic variance..." – radarbob Jul 18 '13 at 15:09
  • 1
    @radarbob: though it raises the question, why isn't there a `ITuple` interface? – TDaver Aug 13 '13 at 11:48
  • 2
    @TDaver: Because Tuples implement IStructuralEquatable, and by so doing they allow you to define your own method for determining equality. You can implement a TupleEqualityComparer that weakens the second rule from the default comparison, so that the generic types don't have to be the same, but the assigned types do. Much less work for the .NET team (though more work for you). – KeithS Aug 14 '13 at 16:53
  • Just beware, this is true for `Equals` only. Tuples dont override `==` hence does a reference check. Nasty surprising behaviour IMO. – nawfal Aug 12 '20 at 14:23
20

Yes, I'd say that's how tuples are supposed to behave. You've got two different tuple types here - Tuple<string> and Tuple<object>.

The documentation for Tuple<T1>.Equals states that two of the conditions are:

  • It is a Tuple<T1> object.
  • Its single component is of the same type as the current instance.

That's not true if you ask whether a Tuple<string> is equal to a Tuple<object>, so it returns false.

In general I think it's a very bad idea for instances of two different types to be deemed equal to each other. It invites all kinds of issues.

Jon Skeet
  • 1,261,211
  • 792
  • 8,724
  • 8,929
  • 1
    Actually, I think it's the second rule specified in the docs; that the single component is of the same type. They're both `Tuple`s, each having only one generic parameter, but the parameters are different so the component types don't match. – KeithS Feb 21 '11 at 18:51
  • @KeithS: It's say it's not a `Tuple`, it's a `Tuple` for some X, but I take your point. I'll add the second bullet point in too :) – Jon Skeet Feb 21 '11 at 19:03
3

Is this really how Tuples are supposed to behave? Is structural compatibility actually an additional constraint on equality as opposed to a relaxation, as I've been interpreting it until now?

Tuple<T1> implements IStructuralEquatable - which, by its name, does exactly that - checks the structure as well as the contents.

You could always rework your Unit test to check the Item contents of the tuple for equality, instead of the Tuple itself.

Reed Copsey
  • 522,342
  • 70
  • 1,092
  • 1,340