c# - IEqualityComparer<T> que utiliza ReferenceEquals
.net (3)
Aquí hay una implementación simple 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 (No tiene que leer esto a menos que esté interesado en los comentarios a continuación)
@AnorZaken dedicó muchos párrafos a las tres letras del new
modificador aquí. Vamos a resumir.
El método Equals(object,object)
instancia única definida Equals(object,object)
implementa el método Equals
de las dos interfaces declaradas para este tipo, IEqualityComparer
y su contraparte genérica IEqualityComparer<object>
. Las firmas son idénticas, por lo que esta definición satisface ambas interfaces.
El método de instancia ReferenceEqualityComparer.Equals(object,object)
oculta el object.Equals(object,object)
estático object.Equals(object,object)
.
Sin new
el compilador avisa de esto. ¿Qué significa esto realmente?
Significa que si desea llamar al objeto estático object.Equals
Métodos object.Equals
, no puede llamarlo en una instancia de ReferenceEqualityComparer
. ¿Es esto un gran problema?
No. De hecho es el comportamiento deseado. Significa que si desea llamar a object.Equals(a,b)
no puede hacerlo a través de un código como ReferenceEqualityComparer.Default.Equals(a,b)
. Ese código está solicitando claramente la igualdad de referencia : nadie esperaría razonablemente que realice la igualdad por defecto / valor. ¿Por qué no codificarías el objeto más explícito? ¿ object.Equals(a,b)
? Por lo tanto, el uso de new
proporciona un comportamiento sensible y deseable, y permite la compilación sin advertencias.
¿De qué otra manera podrías suprimir la advertencia? Si usa una #pragma warning disable 108
/ #pragma warning restore 108
entonces el resultado es el mismo que el uso de un new
, excepto que ha agregado más ruido a su código. new
basta y explica la intención más claramente a los demás.
Alternativamente, podría usar implementaciones explícitas para los dos métodos de interfaz Equals
, pero luego si usara ReferenceEqualityComparer.Default.Equals(a,b)
no tendría la igualdad de referencia en absoluto.
En realidad, ocultar métodos estáticos con métodos de instancia rara vez es un problema porque los métodos estáticos no se mencionan en un especificador de tipo, no en un especificador de instancia. Es decir, utiliza Foo.StaticMethod()
no new Foo().StaticMethod()
. Llamar a métodos estáticos desde instancias es innecesario en el mejor de los casos y engañoso / incorrecto en el peor.
Además, para los comparadores de igualdad, rara vez se utilizan directamente sus tipos concretos. Más bien, los usas con APIs como colecciones.
Entonces, si bien esta fue una discusión interesante y en ocasiones confusa, fue bastante infructuosa.
¿Hay una IEqualityComparer<T>
predeterminada de IEqualityComparer<T>
que use ReferenceEquals
?
EqualityComparer<T>.Default
usa ObjectComparer, que usa object.Equals()
. En mi caso, los objetos ya implementan IEquatable<T>
, que necesito ignorar y comparar solo por la referencia del objeto.
En caso de que no haya una implementación predeterminada, esta es la mía:
Editado por 280Z28: Justificación para usar RuntimeHelpers.GetHashCode(object)
, que muchos de ustedes probablemente no han visto antes. :) Este método tiene dos efectos que lo convierten en la llamada correcta para esta implementación:
- Devuelve 0 cuando el objeto es nulo. Dado que
ReferenceEquals
funciona para parámetros nulos, también debería hacerlo la implementación de GetHashCode () del comparador. - Llama a RuntimeHelpers.GetHashCode(object) no virtualmente.
ReferenceEquals
ignora específicamente cualquier reemplazo deEquals
, por lo que la implementación de GetHashCode () debe usar un método especial que coincida con el efecto de ReferenceEquals, que es exactamente para lo que es RuntimeHelpers.GetHashCode.
[Fin 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
}
Pensé que era hora de actualizar la implementación de las respuestas anteriores a .Net4.0 +, donde se simplifica al volverse no genérico gracias a la contravarianza en la 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);
}
}
Ahora solo es necesario que exista una instancia para todas las comprobaciones de igualdad de referencia en lugar de una para cada tipo T
como fue el caso anteriormente.
¡También guarda la escritura al no tener que especificar T
cada vez que quiera usar esto!
Para aclarar a aquellos que no están familiarizados con los conceptos de covarianza y contraparte ...
class MyClass
{
ISet<MyClass> setOfMyClass = new HashSet<MyClass>(ReferenceEqualityComparer.Default);
}
... funcionará bien Esto no se limita, por ejemplo, a HashSet<object>
o similar (en .Net4.0).
También para cualquiera que se pregunte por qué x == y
es la igualdad de referencia, es porque el operador ==
es un método estático, lo que significa que se resuelve en tiempo de compilación, y en tiempo de compilación xey son de tipo object
por lo que aquí se resuelve en el operador ==
del object
, que es el método de igualdad de referencia real . (De hecho, el Object.ReferenceEquals(object, object)
es simplemente un redireccionamiento al objeto que es igual al operador).