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 в качестве компаратора.