IEqualityComparer que usa ReferenceEquals
-
19-09-2019 - |
Pergunta
Existe uma implementação IEqualityComparer<T>
padrão que usa ReferenceEquals
?
EqualityComparer<T>.Default
usa ObjectComparer, que usa object.Equals()
. No meu caso, os objetos já implementar IEquatable<T>
, o que eu preciso ignorar e comparar por única referência do objeto.
Solução
Apenas no caso não há nenhuma implementação padrão, essa é a minha própria:
Editar por 280Z28: Justificativa para o uso RuntimeHelpers.GetHashCode(object)
, que muitos dos você provavelmente não tenha visto antes. :) Este método tem dois efeitos que tornam o correta ??em> chamada para esta implementação:
- Ele retorna 0 quando o objeto é nulo. Desde
ReferenceEquals
trabalha para parâmetros nulos, assim se a implementação do comparador de GetHashCode (). -
Object.GetHashCode()
não virtualmente.ReferenceEquals
especificamente ignora quaisquer substituições deEquals
, de modo a implementação de GetHashCode () deve usar um método especial que combina o efeito de ReferenceEquals, que é exatamente o que RuntimeHelpers.GetHashCode é para.
[end 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
}
Outras dicas
Eu pensei que era hora de atualizar a implementação respostas anterior para .Net4.0 +, onde ele simplifica, tornando-se graças não-genéricos para contravariance na interface 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);
}
}
Agora só precisa de existir uma instância para toda a verificação em vez de um para cada tipo T
como foi o caso antes de sua referência-igualdade.
Além disso, você poupar digitação por não ter que especificar T
cada vez que você quiser usar este!
Para esclarecer para aqueles que não estão familiarizados com os conceitos de Covariância e Contravariance .. .
class MyClass
{
ISet<MyClass> setOfMyClass = new HashSet<MyClass>(ReferenceEqualityComparer.Default);
}
... vai funcionar muito bem. Esta é não se limitando a, por exemplo, HashSet<object>
ou similar (em .Net4.0).
Também para qualquer um se perguntando por que x == y
é igualdade de referência, é porque o ==
operator é um método estático, o que significa que é resolvido em tempo de compilação e em tempo de compilação x e y são do tipo object
então aqui resolve o ==
operator de object
- que é a real método de igualdade de referência. (Na verdade, o método Object.ReferenceEquals(object, object)
é simplesmente um redirecionamento para o objeto é igual operador.)
Aqui está uma implementação simples para 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);
}
Editar (Você não tem que ler este a menos que você está interessado nos comentários abaixo)
@AnorZaken dedicou muitos parágrafos para as três letras do new
modificador aqui. Vamos resumir.
Os únicos definidos exemplo Equals(object,object)
método implementa o método Equals
das duas interfaces declaradas para este tipo, IEqualityComparer
e sua IEqualityComparer<object>
homólogo genérico. As assinaturas são idênticas, pelo que esta satisfaz definição ambas as interfaces.
O método da ocorrência peles ReferenceEqualityComparer.Equals(object,object)
o estática método object.Equals(object,object)
.
Sem new
o compilador avisa sobre isso. O que isso realmente significa?
Isso significa que se você quiser chamar os métodos object.Equals
estático, você não pode chamá-lo em um exemplo de ReferenceEqualityComparer
. É este um grande negócio?
No. Na verdade, é o comportamento desejado. Isso significa que se você quiser chamar object.Equals(a,b)
você não pode fazê-lo através de código como ReferenceEqualityComparer.Default.Equals(a,b)
. Esse código é claramente solicitando referência igualdade - ninguém seria razoável esperar que ele para executar igualdade padrão / valor. Por que você não apenas codificar o object.Equals(a,b)
mais explícito de qualquer maneira? Assim, o uso de new
fornece comportamento sensato e desejável, e permite a compilação com nenhum aviso.
Como mais você poderia suprimir o aviso? Se você usar um #pragma warning disable 108
/ #pragma warning restore 108
então o resultado é o mesmo que usar new
, exceto que você adicionou mais um monte de ruído ao seu código. sufixos new
e explica a intenção mais claramente para os outros.
Como alternativa, você poderia usar implementações explícitas para os dois métodos de interface Equals
, mas, em seguida, se você ReferenceEqualityComparer.Default.Equals(a,b)
usado você não teria igualdade de referência em tudo.
Na realidade, os métodos de ocultação estáticas com métodos de instância raramente é um problema porque os métodos estáticos são dereferenced de um especificador tipo, não um especificador instância. Ou seja, você usa Foo.StaticMethod()
não new Foo().StaticMethod()
. Chamar métodos estáticos de instâncias é desnecessário no melhor e enganosa / incorreto na pior das hipóteses.
Além disso, para comparadores de igualdade, você raramente usam seus tipos de concreto diretamente. Em vez disso, você usá-los com APIs, tais como coleções.
Assim, embora este foi um interessante e às vezes confundindo discussão, foi bastante infrutíferas.
Microsoft fornecer ObjectReferenceEqualityComparer
em System.Data.Entity.Infrastructure
.
Basta usar ObjectReferenceEqualityComparer.Default
como comparador.