IEqualityComparer<T> that uses ReferenceEquals

10.3k views Asked by At

Is there a default IEqualityComparer<T> implementation that uses ReferenceEquals?

EqualityComparer<T>.Default uses ObjectComparer, which uses object.Equals(). In my case, the objects already implement IEquatable<T>, which I need to ignore and compare by object's reference only.

5

There are 5 answers

8
Yuri Astrakhan On BEST ANSWER

Just in case there is no default implementation, this is my own:

Edit by 280Z28: Rationale for using RuntimeHelpers.GetHashCode(object), which many of you probably haven't seen before. :) This method has two effects that make it the correct call for this implementation:

  1. It returns 0 when the object is null. Since ReferenceEquals works for null parameters, so should the comparer's implementation of GetHashCode().
  2. It calls Object.GetHashCode() non-virtually. ReferenceEquals specifically ignores any overrides of Equals, so the implementation of GetHashCode() should use a special method that matches the effect of ReferenceEquals, which is exactly what RuntimeHelpers.GetHashCode is for.

[end 280Z28]

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

/// <summary>
/// A generic object comparerer that would only use object's reference, 
/// ignoring any <see cref="IEquatable{T}"/> or <see cref="object.Equals(object)"/>  overrides.
/// </summary>
public class ObjectReferenceEqualityComparer<T> : EqualityComparer<T>
    where T : class
{
    private static IEqualityComparer<T> _defaultComparer;

    public new static IEqualityComparer<T> Default
    {
        get { return _defaultComparer ?? (_defaultComparer = new ObjectReferenceEqualityComparer<T>()); }
    }

    #region IEqualityComparer<T> Members

    public override bool Equals(T x, T y)
    {
        return ReferenceEquals(x, y);
    }

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

    #endregion
}
1
Patrick from NDepend team On
9
AnorZaken On

I thought it was time to update the previous answers implementation to .Net4.0+ where generics are no longer needed thanks to contravariance on the IEqualityComparer<in T> interface:

using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

public sealed class ReferenceEqualityComparer
    : IEqualityComparer, IEqualityComparer<object>
{
    private ReferenceEqualityComparer() { }

    public static readonly ReferenceEqualityComparer Default
        = new ReferenceEqualityComparer();

    public /*new*/ bool Equals(object x, object y)
    {
        return x == y; // This is reference equality! (See explanation below)
    }

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

Now there only needs to exist one instance for all your reference-equality checking instead of one for each type T as was the case before.

You no longer have to specify T every time you want to use this and also avoid polluting with unnecessary generic runtime types.


As for why x == y is reference equality, it is because the ==operator is a static method, which means it is resolved at compile-time, and at compile-time the x and y arguments are of type object.

In fact this is what the Object.ReferenceEquals(object, object) method source code looks like:

public static bool ReferenceEquals(object objA, object objB) {
    return objA == objB;
}

To clarify for those who are not familiar with the concepts of Covariance and Contravariance...

class MyClass
{
    ISet<MyClass> setOfMyClass = new HashSet<MyClass>(ReferenceEqualityComparer.Default);
}

...the above code compiles; Notice that it does not say HashSet<object>.

1
renouve On

Microsoft provide ObjectReferenceEqualityComparer in System.Data.Entity.Infrastructure. Just use ObjectReferenceEqualityComparer.Default as comparer.

9
Drew Noakes On

Here's a simple implementation for C# 6 and later:

public sealed class ReferenceEqualityComparer : IEqualityComparer, IEqualityComparer<object>
{
    public static ReferenceEqualityComparer Default { get; } = new ReferenceEqualityComparer();

    public new bool Equals(object x, object y) => ReferenceEquals(x, y);
    public int GetHashCode(object obj) => RuntimeHelpers.GetHashCode(obj);
}

Or a generic version which ensures it is only usable with reference types:

public sealed class ReferenceEqualityComparer<T> : IEqualityComparer<T> where T : class
{
    public static IEqualityComparer<T> Default { get; } = new ReferenceEqualityComparer<T>();

    public bool Equals(T x, T y) => ReferenceEquals(x, y);
    public int GetHashCode(T obj) => RuntimeHelpers.GetHashCode(obj);
}