Kjara
Kjara

Reputation: 2902

generic reference equality comparer does not work for ValueTuple

I have written a generic equality comparer that should always compare by reference, no matter how the GetHashCode and Equals methods of the parameter type look like:

public class ReferenceEqualityComparer<T> : IEqualityComparer<T>
{
    public static ReferenceEqualityComparer<T> Inst = new ReferenceEqualityComparer<T>();
    private ReferenceEqualityComparer() { }
    public bool Equals(T x, T y) { return ReferenceEquals(x, y); }

    public int GetHashCode(T obj) { return RuntimeHelpers.GetHashCode(obj); }
}

I threw in a ValueTuple (in this case a pair of objects) like so:

var objectPairComparer = ReferenceEqualityComparer<(object,object)>.Inst;

But this comparer does not behave as expected, so I guess I am doing something wrong. To see what's wrong, first consider the following situation:

object a = new object();
object b = new object();

object c = a;
object d = b;

HashSet<(object, object)> set = new HashSet<(object, object)>();
Console.WriteLine("set.Add((a, b)) = " + set.Add((a, b))); // returns true
Console.WriteLine("set.Contains((c, d)) = " + set.Contains((c, d))); // returns true
Console.WriteLine("set.Add((c, d)) = " + set.Add((c, d))); // returns false

Since no comparer is given as input to the HashSet, the default comparer will be used. That means, both Item1 and Item2 will use reference equality and default hash code (the address or something). The output is what I expect.

But if I instead use the comparer

HashSet<(object,object)> set = new HashSet<(object,object)>(objectPairComparer);

then the output changes:

Console.WriteLine("set.Add((a, b)) = " + set.Add((a, b))); // returns true like before
Console.WriteLine("set.Contains((c, d)) = " + set.Contains((c, d))); // returns FALSE
Console.WriteLine("set.Add((c, d)) = " + set.Add((c, d))); // returns TRUE

But they should behave the same way! Why don't they? Isn't ReferenceEquals the same as object.Equals, and isn't ReferenceEquals used on two (object,object) the same as using ReferenceEquals both on the Item1s and Item2s and &&ing the results? And GetHashCode analogous?

Upvotes: 0

Views: 580

Answers (1)

Servy
Servy

Reputation: 203820

Isn't ReferenceEquals the same as object.Equals

No, it is not. object.Equals will use virtual dispatch on the first operand, finding it's implementation of Equals for the actual runtime type of the object, and use whatever that type's definition says to do. In the case of ValueTuple, it'll compare the actual values of the two tuples. ReferenceEquals just compares the references and tells you if they're equal. In this particular case you have two different references, even though the value that each reference references are the same.

isn't ReferenceEquals used on two (object,object) the same as using ReferenceEquals both on the Item1s and Item2s and &&ing the results?

No, it's not. It's only going to tell you if the two objects you pass in are both the same reference to the same object. They won't inspect the actual values of those objects. In this case, you have two different references, so they're not equal.

And GetHashCode analogous?

It is analogous, insofar as the first version is using the ValueTuple implementation that computes the hash based on the value of the items within the tuple, while the second computes the hash entirely based on the reference to the object itself, so when you have two different references to two different objects, but where those objects but where those two objects have equivalent values internally, the first considers them equal, the second considers them unequal.

Upvotes: 1

Related Questions