IEqualityComparer ‹T›, использующий ReferenceEquals

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

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


person Yuri Astrakhan    schedule 11.12.2009    source источник
comment
.Net 5.0 представит ReferenceEqualityComparer, как указано в 10 лучших новых версий .NET 5.0 API - пункт 2), который также ссылается на этот вопрос.   -  person Dave Anderson    schedule 23.09.2020


Ответы (5)


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

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

  1. Он возвращает 0, когда объект равен нулю. Поскольку 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
}
person Yuri Astrakhan    schedule 11.12.2009
comment
Единственное, что мне не понравилось в этом, так это то, что свойство Default нарушает предположение о чистоте средства получения свойства. Поскольку среда CLR не будет запускать статический инициализатор для класса, пока не будет сделана ссылка на какой-либо член класса, добавленная мной встроенная инициализация имеет тот же эффективный эффект отложенной инициализации, что и свойство, но не нарушает ограничение чистоты. Я тоже запечатал тип. - person Sam Harwell; 11.12.2009
comment
И последнее, но не менее важное: я унаследовал от EqualityComparer ‹T›, чтобы подобрать реализацию IEqualityComparer (неуниверсальный). Кстати, этот точный тип - это класс internal в сборке System.Xaml из .NET 4 (в пространстве имен System.Xaml.Schema). - person Sam Harwell; 11.12.2009
comment
Я даже не подумал о нулевом случае - спасибо! Что касается Default - EqualityComparer.Default (компаратор по умолчанию, используемый внутри) имеет аналогичную структуру. Думаю, MS не следует своим правилам :) - person Yuri Astrakhan; 11.12.2009
comment
Я разместил соединение с Microsoft, пожалуйста, ГОЛОСОВАТЬ !!! - person Shimmy Weitzhandler; 03.01.2011
comment
Я думаю, что идентификатор Default сбивает с толку. Зачем скрывать унаследованный член без причины. Я бы переименовал свойство Default в Instance (или оставил его полностью, конструктор экземпляра был public). - person Jeppe Stig Nielsen; 03.02.2013
comment
Наследование от EqualityComparer<T> - не лучшая идея. Статическое наследование членов действительно сбивает с толку; так что повторно использовать Default как это - не лучшая идея. Более того, виртуальные методы работают медленнее обычных методов; и поскольку этот тип, скорее всего, будет использоваться в узких циклах, зачем добавлять ненужные накладные расходы? Наконец, реализация IEqualityComparer тривиальна, учитывая то, что у вас уже есть, так почему бы просто не сделать это простым и избежать зависимости? - person Eamon Nerbonne; 15.04.2013
comment
@EamonNerbonne В любом случае методы являются виртуальными вызовами, потому что тип используется в основном исключительно через интерфейс IEqualityComparer ‹T›. Как только метод находится в vtable, не имеет значения, где он реализован, накладные расходы на вызов такие же. - person Jonathan Gilbert; 14.06.2016
comment
@JonathanGilbert: хорошая мысль; в качестве параметра, например, Словарь виртуального или невиртуального значения не имеет. Хотя я по-прежнему считаю, что лучше всего сохранять простоту и избегать ненужного наследования - это не значит, что вы здесь что-то получите за это. - person Eamon Nerbonne; 15.06.2016

Я подумал, что пришло время обновить реализацию предыдущих ответов до .Net4.0 +, где дженерики больше не нужны благодаря контравариантности интерфейса IEqualityComparer<in T>:

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);
    }
}

Теперь должен существовать только один экземпляр для всей вашей проверки на равенство ссылок вместо одного для каждого типа T, как было раньше.

Вам больше не нужно указывать T каждый раз, когда вы хотите использовать это, а также избегать загрязнения ненужными универсальными типами среды выполнения.


Что касается того, почему x == y является ссылочным равенством, это потому, что ==operator является статическим методом, что означает, что он разрешается во время компиляции, а во время компиляции аргументы x и y имеют тип object.

Фактически это то, что Object.ReferenceEquals(object, object) метод source code нравиться:

public static bool ReferenceEquals(object objA, object objB) {
    return objA == objB;
}

Чтобы прояснить для тех, кто не знаком с концепциями Ковариация и Контравариантность ...

class MyClass
{
    ISet<MyClass> setOfMyClass = new HashSet<MyClass>(ReferenceEqualityComparer.Default);
}

... приведенный выше код компилируется; Обратите внимание, что здесь не указано HashSet<object>.

person AnorZaken    schedule 20.02.2016
comment
Цитата: thanks to contravariance on the IEqualityComparer<in T> interface Нет, IEqualityComparer<T> ковариантна в T. - person Tyson Williams; 10.04.2016
comment
@TysonWilliams Нет, я понял. См. MSDN: отклонение в общих интерфейсах. - person AnorZaken; 10.04.2016
comment
Ах да ... nvm (ненавижу возвращаться к этому задом наперед). С другой стороны, теперь рядом с вашим ответом есть еще одна хорошая ссылка (которая мне нравится, кстати ... не знал о RuntimeHelpers.GetHashCode). - person Tyson Williams; 11.04.2016
comment
Это дает предупреждение CS0108: 'ReferenceEqualityComparer.Equals (object, object)' скрывает унаследованный член 'object.Equals (object, object)'. Используйте новое ключевое слово, если предполагалось скрытие. - person Drew Noakes; 15.12.2016
comment
Что ж, вы можете изменить его, добавив ключевое слово new к методу, если хотите ... но это очень субъективное изменение, потому что скрытие на самом деле не предназначено в этом случае - на самом деле это конфликт методов имена - в данном случае это просто безобидное столкновение. Таким образом, использование ключевого слова new здесь будет сигнализировать о неправильных намерениях, и я лично воздержусь от этого. - person AnorZaken; 24.02.2017
comment
Реализуйте интерфейс явно, это решит проблему со скрытием полностью. Думаю, никто не будет использовать ReferenceEqualityComparer.Default.Equals(a, b) напрямую вместо ReferenceEquals(a, b). А если перейти в место, ожидающее IEqualityComparer<T> - все будет хорошо. - person Ivan Danilov; 23.04.2017
comment
Теперь, когда вступила в игру контравариантность, не могли бы мы просто использовать EqualityComparer<object>.Default вместо этого класса? - person relatively_random; 19.12.2019
comment
Нет, потому что он использует возможно переопределенные object.Equals и object.GetHashCode. - person relatively_random; 19.12.2019
comment
Собственно, эти методы виртуальные. Я думаю, вы ответили на свой вопрос, чтобы помочь другим? :) - person AnorZaken; 26.12.2019

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

Или универсальная версия, которая гарантирует, что ее можно использовать только со ссылочными типами:

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);
}
person Drew Noakes    schedule 15.12.2016
comment
Ключевое слово new фактически не должно использоваться. Как сказано в предупреждении "Use the new keyword if hiding was intended.", но скрытие здесь не предназначено, поэтому оно передаст ложные намерения сопровождающим. Это конфликт имен методов, вызывающий непреднамеренное сокрытие, что, к счастью, оказывается безвредным. Правильный курс здесь - отключить это предупреждение для этой строки с помощью прагмы и задокументировать его с комментарием. Кроме того, мне нравится лаконичность C # 6. :) - person AnorZaken; 24.02.2017
comment
Я не согласен. В этом случае new предотвращает предупреждение. Добавьте new, и он компилируется без предупреждения и, что более важно, работает. Во всяком случае, это проблема с диагностикой компилятора. - person Drew Noakes; 24.02.2017
comment
Не проблема с компилятором - предупреждение должно быть - это моя точка зрения. Возможно, используйте прагму для отключения предупреждения, потому что предупреждение заслужено. Вы можете сделать это по-своему для удобства, и, вероятно, никто не будет беспокоиться, но это технически неправильно. Мы можем согласиться или не согласиться с тем, можно ли неправильно использовать здесь new, потому что это безвредно. Но на самом деле я прав, и факты не основаны на мнениях. Чтобы прояснить, это ограничение языка. Мы могли бы использовать явную реализацию интерфейса, чтобы обойти это, но это тоже может быть нежелательно. Безвредно! = Ложное предупреждение. - person AnorZaken; 24.02.2017
comment
Тот факт, что это работает, является данностью, поскольку использование ключевого слова new является чисто косметическим свойством, которое язык хочет, чтобы вы использовали для подтверждения своих намерений. Это не оказывает никакого влияния на созданный код. Он существует только для людей. Наше намерение здесь - реализовать IEqualityComparer<object>.Equals не, чтобы скрывать Object.Equals. Использование new сигнализирует о последнем, что неверно. - person AnorZaken; 24.02.2017
comment
Метод не скрывает Object.Equals. Проблема заключается в конфликте между IEqualityComparer.Equals и IEqualityComparer<object>.Equals, которые имеют одинаковые подписи. Возможные варианты: повторяющиеся члены (с явной реализацией), добавление #pragma или просто использование new. В случае сомнений я обычно предпочитаю решение, в котором используется меньше кода, поскольку меньше кода приводит к меньшему количеству ошибок. Каждому свое. Но, как вы говорите, в этой реализации нет ничего технически некорректного. Чтобы убедиться, проверьте MSIL. - person Drew Noakes; 24.02.2017
comment
Вы снова ошибаетесь, вы процитировали предупреждение как: Это дает предупреждение CS0108: 'ReferenceEqualityComparer.Equals (object, object)' скрывает унаследованный член 'object.Equals (object, object)'. себя в старый комментарий к моему ответу выше. Компилятор не солгал! Мы скрываем метод public static bool Equals(object, object). ‹- (ссылка MSDN). - person AnorZaken; 25.02.2017
comment
Я вижу здесь путаницу (static / instance object.Equals). Но как скрыть более одного метода? - person Drew Noakes; 26.02.2017
comment
Вы сказали, что скрываются два метода. Я вижу только одно: экземпляр Equals, скрывающий статическое Equals (арность два). Я что-то упускаю? - person Drew Noakes; 27.02.2017
comment
Общая версия бессмысленна. Дело в том, что IEqualityComparer контравариантен для универсального параметра, так что компаратор типа базового класса может также обрабатывать типы дочернего класса. IEqualityComparer<FooBar> comp = ReferenceEqualityComparer.Default; будет работать так, как задумано. Общая версия просто загрязняет типы среды выполнения - это было решено в .Net4, что было основным улучшением, отмеченным в моем старом ответе выше. Вы беспокоитесь о боксе? Не будь. Мы проверяем эталонное равенство. Бокс встроен в ReferenceEquals. - person AnorZaken; 10.05.2021

В .NET 5.0 теперь у вас есть System.Collections.Generic. ReferenceEqualityComparer

person Patrick from NDepend team    schedule 18.09.2020

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

person renouve    schedule 28.08.2019