IEqualityComparer que utiliza ReferenceEquals

¿Hay una IEqualityComparer predeterminada de IEqualityComparer que utiliza ReferenceEquals ?

EqualityComparer.Default utiliza ObjectComparer, que usa object.Equals() . En mi caso, los objetos ya implementan IEquatable , que necesito ignorar y comparar solo por referencia del objeto.

En caso de que no haya una implementación predeterminada, esto es mío:

Editar por 280Z28: Justificación para usar RuntimeHelpers.GetHashCode(object) , que probablemente muchos de ustedes no hayan visto antes. 🙂 Este método tiene dos efectos que lo convierten en la llamada correcta para esta implementación:

  1. Devuelve 0 cuando el objeto es nulo. Dado que ReferenceEquals funciona para parámetros nulos, también debe hacerlo la implementación del comparador de GetHashCode ().
  2. Llama a Object.GetHashCode() no virtual. ReferenceEquals ignora específicamente cualquier sustitución de Equals , por lo que la implementación de GetHashCode () debería 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; ///  /// A generic object comparerer that would only use object's reference, /// ignoring any  or  overrides. ///  public class ObjectReferenceEqualityComparer : EqualityComparer where T : class { private static IEqualityComparer _defaultComparer; public new static IEqualityComparer Default { get { return _defaultComparer ?? (_defaultComparer = new ObjectReferenceEqualityComparer()); } } #region IEqualityComparer 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 el momento de actualizar la implementación de las respuestas anteriores a .NET4.0 + donde se simplifica al convertirse en no genérico gracias a la contravarianza en la IEqualityComparer :

 using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; public sealed class ReferenceEqualityComparer : IEqualityComparer, IEqualityComparer { 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 necesita existir una instancia para todas las comprobaciones de igualdad de referencias en lugar de una para cada tipo T como era el caso anterior.

¡También ahorras tipeando al no tener que especificar T cada vez que quieres usar esto!


Para aclarar para aquellos que no están familiarizados con los conceptos de Covarianza y Contravarianza ...

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

... funcionará bien. Esto no está limitado, por ejemplo, a HashSet o similar (en .Net4.0).


También para cualquiera que se pregunte por qué x == y es igualdad de referencia, es porque el operador == es un método estático, lo que significa que se resuelve en tiempo de comstackción, y en tiempo de comstackción xey son de tipo object así que aquí resuelve el operador == del object , que es el método de referencia real de igualdad. (De hecho, el Object.ReferenceEquals(object, object) es simplemente una redirección al objeto igual a operador).

Aquí hay una implementación simple para C # 6.

 public sealed class ReferenceEqualityComparer : IEqualityComparer, IEqualityComparer { 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)

@AnZZaken dedicó muchos párrafos a las tres letras del new modificador aquí. Vamos a resumir.

El único método de Equals(object,object) instancia Equals(object,object) implementado implementa el método Equals de las dos interfaces declaradas para este tipo, IEqualityComparer y su contraparte genérica IEqualityComparer . 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) static object.Equals(object,object) .

Sin new el comstackdor advierte sobre esto. ¿Qué significa esto realmente?

Significa que si desea llamar al objeto estático object.Equals Métodos de object.Equals , no puede object.Equals en una instancia de ReferenceEqualityComparer . ¿Esto es un gran problema?

No. De hecho, es un 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 de valor por defecto. ¿Por qué no codificaría el objeto más explícito. object.Equals(a,b) todos modos? Entonces, el uso de new proporciona un comportamiento sensible y deseable, y permite la comstackción sin advertencias.

¿De qué otra manera podrías suprimir la advertencia? Si usa una #pragma warning disable 108 / #pragma warning restre 108 entonces el resultado es el mismo que usar new , excepto que ha agregado un montón de ruido a su código. new suficiente y explica la intención más claramente a los demás.

De forma alternativa, podría utilizar implementaciones explícitas para los dos métodos Equals interfaz, pero si utilizara ReferenceEqualityComparer.Default.Equals(a,b) no tendría ninguna igualdad de referencia.

En realidad, ocultar métodos estáticos con métodos de instancia rara vez es un problema porque los métodos estáticos se eliminan de un especificador de tipo, no como un especificador de instancia. Es decir, usa Foo.StaticMethod() no new Foo().StaticMethod() . Llamar a los métodos estáticos de las instancias es innecesario en el mejor de los casos y engañoso / incorrecto en el peor.

Además, para los que compiten por la igualdad, rara vez usa sus tipos concretos directamente. Por el contrario, los usa con API, como colecciones.

Entonces, aunque esta fue una discusión interesante y en ocasiones confusa, fue bastante infructuosa.