Вопрос

Есть ли значение по умолчанию IEqualityComparer<T> реализация, которая использует ReferenceEquals?

EqualityComparer<T>.Default использует ObjectComparer, который использует object.Equals().В моем случае объекты уже реализуют IEquatable<T>, который мне нужно игнорировать и сравнивать только по ссылке на объект.

Это было полезно?

Решение

На всякий случай нет реализации по умолчанию, это моя собственная:

Редактировал 280Z28:Обоснование использования RuntimeHelpers.GetHashCode(object), который многие из вас, вероятно, еще не видели.:) Этот метод имеет два эффекта, которые делают его правильный вызовите эту реализацию:

  1. Он возвращает 0, когда объект имеет значение NULL.С ReferenceEquals работает для нулевых параметров, так же должна работать и реализация GetHashCode() компаратора.
  2. Он вызывает 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'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
}

Другие советы

Я подумал, что пришло время обновить реализацию предыдущих ответов до .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) метод — это просто перенаправление на оператор равенства объекта.)

Вот простая реализация для 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);
}

РЕДАКТИРОВАТЬ (Вам не обязательно это читать, если вас не интересуют комментарии ниже)

@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() нет new Foo().StaticMethod().Вызов статических методов из экземпляров в лучшем случае не нужен, а в худшем — вводит в заблуждение/некорректен.

Кроме того, для компараторов на равенство вы редко используете их конкретные типы напрямую.Скорее, вы используете их с API, такими как коллекции.

Так что, хотя это была интересная и порой запутанная дискуссия, она оказалась довольно бесплодной.

Microsoft предоставляет ObjectReferenceEqualityComparer в System.Data.Entity.Infrastructure.Просто используйте ObjectReferenceEqualityComparer.Default в качестве сравнения.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top