IEqualityComparer <T>, который использует ReferenceEquals
Есть ли реализация по умолчанию IEqualityComparer<T>
, которая использует ReferenceEquals
?
EqualityComparer<T>.Default
использует ObjectComparer, который использует object.Equals()
. В моем случае объекты уже реализуют IEquatable<T>
, которые мне нужно игнорировать и сравнивать только по объектной ссылке.
Ответы
Ответ 1
На всякий случай, если нет реализации по умолчанию, это мое:
Изменить на 280Z28: Обоснование использования RuntimeHelpers.GetHashCode(object)
, о котором многие из вас, вероятно, раньше не видели.:) Этот метод имеет два эффекта, которые делают его правильным вызовом для этой реализации:
- Он возвращает 0, когда объект имеет значение null. Поскольку
ReferenceEquals
работает для нулевых параметров, так же должна выполняться реализация сравнения GetHashCode().
- Он вызывает
Object.GetHashCode()
не виртуально. ReferenceEquals
специально игнорирует любые переопределения Equals
, поэтому реализация GetHashCode() должна использовать специальный метод, который соответствует эффекту ReferenceEquals, что и есть для RuntimeHelpers.GetHashCode.
[конец 280Z28]
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
/// <summary>
/// A generic object comparerer that would only use object 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
}
Ответ 2
Я подумал, что пришло время обновить реализацию предыдущих ответов .Net4.0 +, где это упрощается, становясь неосновным благодаря контравариантности в интерфейсе IEqualityComparer<in T>
:
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);
}
}
Теперь существует только один экземпляр для всей проверки ссылочного равенства вместо одного для каждого типа T
, как это было раньше.
Также вы сохраняете ввод, не указывая T
каждый раз, когда хотите использовать это!
Прояснить для тех, кто не знаком с понятиями Ковариация и контравариантность...
class MyClass
{
ISet<MyClass> setOfMyClass = new HashSet<MyClass>(ReferenceEqualityComparer.Default);
}
... будет работать нормально. Это не, ограниченное, например, HashSet<object>
или аналогичный (в .Net4.0).
Также для тех, кто задается вопросом, почему x == y
является ссылочным равенством, это потому, что оператор ==
является статическим методом, что означает, что он разрешен во время компиляции, а во время компиляции x и y имеют тип object
, поэтому здесь он решается с оператором ==
object
- который является реальным эталонным методом равенства. (На самом деле метод Object.ReferenceEquals(object, object)
является просто перенаправлением на объект, равным оператору.)
Ответ 3
Здесь простая реализация для С# 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 (вам не обязательно читать это, если вы не заинтересованы в комментариях ниже)
@AnorZaken посвятил много абзацев трем буквам модификатора new
здесь. Подведем итоги.
Метод одиночного определенного экземпляра Equals(object,object)
реализует метод Equals
для двух объявленных интерфейсов для этого типа, IEqualityComparer
и его общий аналог IEqualityComparer<object>
. Подписи идентичны, поэтому это определение удовлетворяет обеим интерфейсам.
Метод экземпляра ReferenceEqualityComparer.Equals(object,object)
скрывает статический метод object.Equals(object,object)
.
Без new
компилятор предупреждает об этом. Что это значит?
Это означает, что если вы хотите вызвать статические методы object.Equals
, вы не можете вызвать его в экземпляре ReferenceEqualityComparer
. Это большая сделка?
Нет. На самом деле это желаемое поведение. Это означает, что если вы хотите вызвать object.Equals(a,b)
, вы не можете сделать это с помощью кода, например ReferenceEqualityComparer.Default.Equals(a,b)
. Этот код явно требует ссылочного равенства - никто не будет разумно ожидать, что он выполнит равенство по умолчанию/ценности. Почему бы вам просто не закодировать более явный object.Equals(a,b)
? Поэтому использование new
обеспечивает разумное и желательное поведение и позволяет компиляцию без предупреждений.
Как еще вы могли бы подавить предупреждение? Если вы используете #pragma warning disable 108
/#pragma warning restore 108
, тогда результат будет таким же, как при использовании new
, за исключением того, что вы добавили в свой код больше шума. new
достаточно и объясняет намерение более ясно другим.
В качестве альтернативы вы можете использовать явные реализации для двух интерфейсных методов Equals
, но если вы использовали ReferenceEqualityComparer.Default.Equals(a,b)
, у вас не было бы никакого равенства ссылок.
В действительности, скрытие статических методов с помощью методов экземпляров редко является проблемой, потому что статические методы разыменовываются из спецификатора типа, а не спецификатора экземпляра. То есть вы используете Foo.StaticMethod()
not new Foo().StaticMethod()
. Вызов статических методов из экземпляров в лучшем случае не нужен, а в худшем - неправильный/неверный.
Кроме того, для сопоставлений равенства вы редко используете их конкретные типы напрямую. Скорее, вы используете их с API, такими как коллекции.
Таким образом, хотя это было интересное и порой запутанное обсуждение, это было довольно бесплодно.
Ответ 4
Microsoft предоставляет ObjectReferenceEqualityComparer
в System.Data.Entity.Infrastructure
.
Просто используйте ObjectReferenceEqualityComparer.Default
в качестве компаратора.