IEqualityComparer, который использует ReferenceEquals


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

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

3 53

3 ответа:

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

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

  1. он возвращает 0, когда объект равен нулю. Так как ReferenceEquals работает для нулевых параметров, так что следует реализация компаратора GetHashCode().
  2. It звонит 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);
}

EDIT (вы не должны читать это, если вы заинтересованы в комментариях ниже.)

@AnorZaken посвятил много абзацев трем буквам модификатор. Давайте подведем итоги.

единственный определенный экземпляр 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, такими как коллекции.

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