IEqualityComparer<T>, который использует ReferenceEquals
Есть ли дефолт IEqualityComparer<T>
реализация, которая использует ReferenceEquals
?
EqualityComparer<T>.Default
использует ObjectComparer, который использует object.Equals()
, В моем случае объекты уже реализованы IEquatable<T>
, который мне нужно игнорировать и сравнивать только по ссылке на объект.
3 ответа
На всякий случай нет реализации по умолчанию, это мое собственное:
Изменить на 280Z28: Обоснование использования RuntimeHelpers.GetHashCode(object)
что многие из вас, вероятно, не видели раньше.:) Этот метод имеет два эффекта, которые делают его правильным вызовом для этой реализации:
- Возвращает 0, когда объект нулевой. поскольку
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'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)
Метод - это просто перенаправление на оператор равных объектов.)
В.NET 5.0 теперь у вас есть https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.referenceequalitycomparer
Вот простая реализация для 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
в качестве компаратора.