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.
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:
- It returns 0 when the object is null. Since
ReferenceEquals
works for null parameters, so should the comparer's implementation of GetHashCode(). - It calls
Object.GetHashCode()
non-virtually.ReferenceEquals
specifically ignores any overrides ofEquals
, 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
}
I thought it was time to update the previous answers implementation to .Net4.0+ where it simplifies by becoming non-generic 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>
{
public static readonly ReferenceEqualityComparer Default
= new ReferenceEqualityComparer(); // JIT-lazy is sufficiently lazy imo.
private ReferenceEqualityComparer() { } // <-- A matter of opinion / style.
public 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.
Also you save typing by not having to specify T
every time you want to use this!
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);
}
...will work just fine. This is not limited to e.g. HashSet<object>
or similar (in .Net4.0).
Also for anyone wondering 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 x and y are of type object
so here it resolves to the ==
operator of object
- which is the real reference equality method. (In fact the Object.ReferenceEquals(object, object)
method is simply a redirect to the object equals operator.)
Here's a simple implementation for C# 6.
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);
}
EDIT (You don't have to read this unless you're interested in the comments below)
@AnorZaken devoted many paragraphs to the three letters of the new
modifier here. Let's summarise.
The single defined instance Equals(object,object)
method implements the Equals
method of the two declared interfaces for this type, IEqualityComparer
and its generic counterpart IEqualityComparer<object>
. The signatures are identical, so this definition satisfies both interfaces.
The instance method ReferenceEqualityComparer.Equals(object,object)
hides the static object.Equals(object,object)
method.
Without new
the compiler warns about this. What does this actually mean?
It means that if you want to call the static object.Equals
methods, you cannot call it on an instance of ReferenceEqualityComparer
. Is this a big deal?
No. In fact it's desired behaviour. It means that if you want to call object.Equals(a,b)
you cannot do it via code such as ReferenceEqualityComparer.Default.Equals(a,b)
. That code is clearly requesting reference equality -- no one would reasonably expect it to perform default/value equality. Why wouldn't you just code the more explicit object.Equals(a,b)
anyway? So the use of new
provides sensible and desirable behaviour, and allows compilation with no warnings.
How else could you suppress the warning? If you use a #pragma warning disable 108
/#pragma warning restore 108
then the outcome is the same as using new
, except you've added a bunch more noise to your code. new
suffices and explains the intent more clearly to others.
Alternatively you could use explicit implementations for the two interface Equals
methods, but then if you used ReferenceEqualityComparer.Default.Equals(a,b)
you wouldn't have reference equality at all.
In reality, hiding static methods with instance methods is rarely a problem because static methods are dereferenced from a type specifier, not an instance specifier. That is, you use Foo.StaticMethod()
not new Foo().StaticMethod()
. Calling static methods from instances is unnecessary at best and misleading/incorrect at worst.
Further, for equality comparers, you rarely use their concrete types directly. Rather, you use them with APIs such as collections.
So whilst this was an interesting and at times confusing discussion, it was rather fruitless.
来源:https://stackoverflow.com/questions/1890058/iequalitycomparert-that-uses-referenceequals