65

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.

Liam
  • 27,717
  • 28
  • 128
  • 190
Yuri Astrakhan
  • 8,808
  • 6
  • 63
  • 97
  • 5
    .Net 5.0 will introduce [ReferenceEqualityComparer](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.referenceequalitycomparer?view=net-5.0) as mentioned in [Top 10 .NET 5.0 new APIs - item 2)](https://blog.ndepend.com/top-10-net-5-0-new-apis/) which also references this question. – Dave Anderson Sep 23 '20 at 04:11

5 Answers5

61

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
}
Snowbear
  • 16,924
  • 3
  • 43
  • 67
Yuri Astrakhan
  • 8,808
  • 6
  • 63
  • 97
  • 1
    The only thing I didn't like about this is the `Default` property violates the purity assumption of a property getter. Since the CLR won't run the static initializer for the class until some member of the class is referenced, the inline initialization I added has the same effective lazy initialization effect as the property did but doesn't violate the purity constraint. I also sealed the type. – Sam Harwell Dec 11 '09 at 19:31
  • 1
    Last but not least, I derived from EqualityComparer to pick up the implementation of IEqualityComparer (non-generic). On a side note, this exact type is an `internal` class in the System.Xaml assembly from .NET 4 (in the System.Xaml.Schema namespace). – Sam Harwell Dec 11 '09 at 19:41
  • I didn't even think of the null case - thanks! As for Default - EqualityComparer.Default (default comparerer used internally) has similar structure. Guess MS is not following its own guidelines :) – Yuri Astrakhan Dec 11 '09 at 19:54
  • I've posted a connection to Microsoft, please [VOTE](http://connect.microsoft.com/VisualStudio/feedback/details/634394 "Include a ReferenceComparer that compares for ReferenceEquals in the BCL")!!! – Shimmy Weitzhandler Jan 03 '11 at 14:52
  • 1
    I think the `Default` identifier is confusing. Why hide an inherited member for no reason. I would rename the `Default` property to `Instance` (or leave it out completely, the instance constructor being `public`). – Jeppe Stig Nielsen Feb 03 '13 at 08:46
  • 7
    Inheriting from `EqualityComparer` isn't a good idea. Static member "inheritance" is really confusing; so reusing `Default` like this isn't a good idea. Furthermore, virtual methods are slower than normal methods; and since this is likely a type that'll be used in tight loops, why add unnecessary overhead? Finally, implementing `IEqualityComparer` is trivial given what you already have, so why not just keep it straightforward and avoid the dependency? – Eamon Nerbonne Apr 15 '13 at 09:21
  • 3
    @EamonNerbonne The methods are virtual calls anyway, though, because the type is used pretty much exclusively via the IEqualityComparer interface. Once the method is in the vtable, it doesn't matter where it's implemented, the call overhead is the same. – Jonathan Gilbert Jun 14 '16 at 20:14
  • @JonathanGilbert: good point; as a parameter to e.g. a Dictionary virtual vs. nonvirtual won't matter. Although I still think it's best practice to keep it simple and avoid needless inheritance - it's not like you're getting anything for it here. – Eamon Nerbonne Jun 15 '16 at 08:42
21

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>.

AnorZaken
  • 1,916
  • 1
  • 22
  • 34
  • Quote: "`thanks to contravariance on the IEqualityComparer interface`" No, `IEqualityComparer` is covariant in `T`. – Tyson Williams Apr 10 '16 at 19:28
  • 1
    @TysonWilliams Nope I got it right. See [MSDN: Variance in Generic Interfaces](https://msdn.microsoft.com/en-us/library/dd233059.aspx). – AnorZaken Apr 10 '16 at 20:58
  • Ah, yes..nvm (I hate getting this backwards). On the plus side, there is now another good link adjacent to your answer (which I like btw...didn't know about `RuntimeHelpers.GetHashCode`). – Tyson Williams Apr 10 '16 at 21:06
  • 1
    This gives warning CS0108: 'ReferenceEqualityComparer.Equals(object, object)' hides inherited member 'object.Equals(object, object)'. Use the new keyword if hiding was intended. – Drew Noakes Dec 15 '16 at 16:54
  • 1
    Well you could change it by adding the `new` keyword to the method if you want... but it's a highly subjective change, because hiding _isn't actually intended_ in this case - it's actually a clash of method names - it just happens to be a harmless clash in this case. So using the `new` keyword here would signal incorrect intentions and I personally would refrain from doing so. – AnorZaken Feb 24 '17 at 08:07
  • 2
    Implement the interface explicitly, it will solve issue with hiding completely. I guess no one will use `ReferenceEqualityComparer.Default.Equals(a, b)` directly instead of `ReferenceEquals(a, b)`. And if passing to a place expecting `IEqualityComparer` - everthing would be fine. – Ivan Danilov Apr 23 '17 at 11:49
  • Now that contravariance is in play, couldn't we just use `EqualityComparer.Default` instead of this class? – relatively_random Dec 19 '19 at 16:21
  • 1
    No, because it uses the possibly overridden `object.Equals` and `object.GetHashCode`. – relatively_random Dec 19 '19 at 16:34
  • Exactly, those methods are virtual. I guess you answered your own question to help others? :) – AnorZaken Dec 26 '19 at 14:05
14

In .NET 5.0 you now have System.Collections.Generic.ReferenceEqualityComparer

Patrick from NDepend team
  • 13,237
  • 6
  • 61
  • 92
  • [https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/System/Collections/Generic/ReferenceEqualityComparer.cs](https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/System/Collections/Generic/ReferenceEqualityComparer.cs) – AnorZaken May 10 '21 at 03:09
11

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);
}
Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
  • The `new` keyword should actually _not_ be used. As the warning says `"Use the new keyword if hiding was intended."` but hiding isn't intended here so it will convey false intentions to maintainers. This is a method name clash, causing _un_-intentional hiding, which very fortunately happens to be harmless. The correct course here would be to turn that warning off for that line with a pragma and documenting it with a comment. Aside from that I'm loving the C#6 succinctness. :) – AnorZaken Feb 24 '17 at 08:12
  • I disagree. In this case, `new` prevents a warning. Add `new` and it compiles without warning and, more importantly, works. If anything, this is an issue with the compiler diagnostic. – Drew Noakes Feb 24 '17 at 08:20
  • Not an issue with the compiler - the warning _should be there_ - that's my point. Possibly use a pragma to disable the warning, because the warning is _deserved_. You can do it your way for convenience and probably no one will be bothered, but it's technically wrong. We can agree or disagree on if it's ok to _incorrectly_ use `new` here because it's harmless. But factually I'm correct and facts are not opinion based. To clarify this is a limitation of the language. We could use explicit interface implementation to get around it but that might not be desirable either. Harmless != False warning. – AnorZaken Feb 24 '17 at 08:47
  • 1
    The fact that it works is a given since using the `new` keyword is purely a cosmetic property that the language wants you to use to confirm your intentions. It has zero effect on the produced code. It exists for humans only. Our intention here is to implement `IEqualityComparer.Equals` **not** to hide `Object.Equals`. Using `new` signals the latter, which is wrong. – AnorZaken Feb 24 '17 at 08:50
  • The method is not hiding `Object.Equals`. The issue is a collision between `IEqualityComparer.Equals` and `IEqualityComparer.Equals` which have identical signatures. Options are: duplicate members (with explicit implementations), add `#pragma`s, or just use `new`. When in doubt, I usually favour the solution that uses less code, as less code tends to result in fewer bugs. Each to their own. But there is nothing technically incorrect about this implementation, as you say. Check [the MSIL](https://gist.github.com/drewnoakes/665565e61d3c651e58e98ee5872a2909) to be sure. – Drew Noakes Feb 24 '17 at 11:04
  • You are wrong again, you quoted the warning as: **"This gives warning CS0108: 'ReferenceEqualityComparer.Equals(object, object)' hides inherited member 'object.Equals(object, object)'."** yourself in an old comment on my answer above. The compiler didn't lie! We are hiding the method [`public static bool Equals(object, object)`](https://msdn.microsoft.com/en-us/library/w4hkze5k(v=vs.110).aspx). <--(MSDN link). – AnorZaken Feb 24 '17 at 22:53
  • I see the confusion here (static/instance object.Equals). But how is more than one method being hidden? – Drew Noakes Feb 26 '17 at 13:47
  • You said there are two methods being hidden. I can only see one: the instance `Equals` hiding the static `Equals` (arity two). Am I missing something? – Drew Noakes Feb 26 '17 at 21:01
  • The generic version is pointless. The point of IEqualityComparer being contravariant on the generic parameter is so a comparer of base-class type can handle child-class types too. `IEqualityComparer comp = ReferenceEqualityComparer.Default;` will work just as intended. The generic version just pollutes the runtime types - this was solved in .Net4 which was the major improvement highlighted in my old answer above. Are you worried about boxing? Don't be. We are testing *reference* equality. The boxing is baked into ReferenceEquals. – AnorZaken May 10 '21 at 02:57
0

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

renouve
  • 90
  • 3