2009-11-19 24 views
37

Estoy trabajando en un proyecto C# en el que, hasta ahora, he usado objetos y fábricas inmutables para asegurar que los objetos de tipo Foo siempre se puedan comparar por igualdad con ==.Operador sobrecarga == versus Igual()

Foo los objetos no se pueden cambiar una vez creados, y la fábrica siempre devuelve el mismo objeto para un conjunto determinado de argumentos. Esto funciona muy bien, y en toda la base de código suponemos que == siempre funciona para verificar la igualdad.

Ahora necesito agregar algunas funcionalidades que presenten un caso de borde para el que esto no siempre funcionará. Lo más fácil es sobrecargar operator == para ese tipo, de modo que ninguno de los otros códigos en el proyecto necesite cambiar. Pero esto me parece un olor a código: sobrecargar operator == y no Equals me parece extraño, y estoy acostumbrado a la convención de que == comprueba la igualdad de referencia, y Equals comprueba la igualdad de objeto (o cualquiera que sea el término).

¿Es esto una preocupación legítima, o debería seguir adelante y sobrecargar operator ==?

+0

A propósito, vb.net prohíbe el uso de sus operadores de igualdad '=' y '<>' para los tipos que no proporcionan sobrecargas explícitas; para verificar la igualdad de referencia, uno usa 'Is' o' IsNot', que esencialmente siempre comprueba la igualdad de referencia (siendo la principal excepción cuando se comparan tipos anulables a 'Nothing'). – supercat

Respuesta

26

Creo que el estándar es que para la mayoría de los tipos, .Equals verifica la similitud de los objetos, y el operador == comprueba la igualdad de referencia.

Creo que la mejor práctica es que para los tipos inmutables, el operador == debe verificar la similitud, así como .Equals. Y si quiere saber si realmente son el mismo objeto, use .ReferenceEquals. Consulte la clase C# String para ver un ejemplo de esto.

+4

No. El 'estándar' para los tipos de referencia es para ambos Iguales en == para devolver lo mismo que RefernceEquals. Se desaconseja sobrecargar tanto para tipos no inmutables. –

+2

Además, tenga cuidado con los nulos aquí. x.Equals (null) lanzará un NRE si x es nulo, mientras que null == x funcionará. – darthtrevino

+0

'estándar' es una forma común de resolver cierto tipo de problema, si el 'estándar' se interpone, entonces no es un estándar muy bueno. –

7

Para tipos inmutables, no creo que haya nada de malo en tener == sobrecargado para soportar la igualdad de valores. No creo que anule == sin anular Equals para tener la misma semántica. Si sobrescribe == y necesita verificar la igualdad de referencia por alguna razón, puede usar Object.ReferenceEquals(a,b).

ver este Microsoft article for some useful guidelines

+0

Solo es una buena idea si el tipo inmutable también está SELLADO. Si el tipo puede ser subclasificado, es muy lamentable que WHICH "==" se llame está determinado por la declaración de variable COMPILE-TIME, NO por el tipo dinámico en tiempo de ejecución (que podría ser la subclase). – ToolmakerSteve

7

Definitivamente huele. Al sobrecargar ==, debe asegurarse de que tanto Equals() como GetHashCode() también sean consistentes. Ver el MSDN guidelines.

Y la única razón por la que esto parece correcto es porque usted describe su tipo como inmutable.

+0

Estoy comparando un atributo con un métodoinfo. Esos son semánticamente inmutables. –

81

Hay una gran diferencia entre sobrecarga== y anulando iguales.

Cuando se tiene la expresión

if (x == y) { 

El método que se utilizó para comparar variables x e y se decide en compilar tiempo. Esta es la sobrecarga del operador. El tipo utilizado al declarar xey se usa para definir qué método se usa para compararlos. El tipo real dentro de x y y (es decir, una subclase o implementación de interfaz) es irrelevante. Considera lo siguiente.

object x = "hello"; 
object y = 'h' + "ello"; // ensure it's a different reference 

if (x == y) { // evaluates to FALSE 

y la siguiente

string x = "hello"; 
string y = 'h' + "ello"; // ensure it's a different reference 

if (x == y) { // evaluates to TRUE 

Esto demuestra que el tipo utiliza para declarar las variables x e y se utiliza para determinar qué método se utiliza para evaluar ==.

En comparación, Equals se determina en runtime basado en el tipo real dentro de la variable x. Equals es un método virtual en Object que otros tipos pueden, y lo hacen, anular. Por lo tanto, los dos ejemplos siguientes se evalúan como verdaderos.

object x = "hello"; 
object y = 'h' + "ello"; // ensure it's a different reference 

if (x.Equals(y)) { // evaluates to TRUE 

y la siguiente

string x = "hello"; 
string y = 'h' + "ello"; // ensure it's a different reference 

if (x.Equals(y)) { // also evaluates to TRUE 
+1

Este es un punto muy importante, y es algo que encontré más tarde mientras estaba probando esto. Como necesitaba 'operator ==' para depender del tipo de tiempo de ejecución, definí una sobrecarga de operador en la clase base que llamaba 'Equals()'. Trabajado como un encanto. (Luego rehice el modelo de objeto y lo arranqué por completo. Pero esa es una historia diferente.) –

+1

Estoy totalmente en desacuerdo. El uso de '==' es una expresión común. El hecho de que C# base todas las entidades a 'objeto' no significa que debemos abandonar la expresión idiomática para evitar la situación en esta publicación. Más bien, se convierte en una buena práctica usar '.Equals()' cuando se necesita una comparación en la parte superior de la jerarquía. – drdwilcox

+1

En resumen, sobrecarga == para establecer qué método llamar (Object.Equals u Object.ReferenceEqual). Overload Object.Equals para verificar la igualdad del valor real. ¿Estoy en lo cierto? –

4

Microsoft de acuerdo con el propietario de Best prácticas del resultado del método Equals y los iguales (==) de sobrecarga debe ser el mismo.

CA2224: Override equals on overloading operator equals

+2

En casi todas las circunstancias en las que se sobrecarga el ''Ent =' 'enlazado estáticamente uno también debe anular el miembro' Equals' virtual, pero el reverso no es verdadero. Uno solo debería sobrecargar '==' para los tipos * sealed * que se comportan de forma muy parecida a los valores (del mismo modo que lo hace 'String'), pero a menudo se debe anular' Equals' para los tipos no sellados. Tenga en cuenta que muchos programadores esperan que aplicar el operador '==' a cualquier tipo de clase que no sea 'cadena' sea una forma abreviada de verificar la igualdad de referencia. – supercat

4

ejemplo que muestra cómo implementar esta de acuerdo con MSFT guidelines (abajo). Tenga en cuenta que cuando anule Igual también debe anular GetHashCode(). Espero que esto ayude a la gente.

public class Person 
{ 
    public Guid Id { get; private set; } 

    public Person(Guid id) 
    { 
     Id = id; 
    } 

    public Person() 
    { 
     Id = System.Guid.NewGuid(); 
    } 

    public static bool operator ==(Person p1, Person p2) 
    { 
     bool rc; 

     if (System.Object.ReferenceEquals(p1, p2)) 
     { 
      rc = true; 
     } 
     else if (((object)p1 == null) || ((object)p2 == null)) 
     { 
      rc = false; 
     } 
     else 
     { 
      rc = (p1.Id.CompareTo(p2.Id) == 0); 
     } 

     return rc; 
    } 

    public static bool operator !=(Person p1, Person p2) 
    { 
     return !(p1 == p2); 
    } 

    public override bool Equals(object obj) 
    { 
     bool rc = false; 
     if (obj is Person) 
     { 
      Person p2 = obj as Person; 
      rc = (this == p2); 
     } 
     return rc; 
    } 

    public override int GetHashCode() 
    { 
     return Id.GetHashCode(); 
    } 
} 
+0

Su verificación nula es defectuosa. Si * ambas * personas son nulas, devolverá falso cuando debería ser verdadero. Desea utilizar un OR exclusivo entre los dos controles nulos en lugar de un OR booleano. – Servy

+2

ReferenceEquals devolverá verdadero si ambos p1 y p2 son nulos.Por lo tanto, el código OR booleano nunca se ejecutará y el operador devolverá verdadero como se esperaba. – friederbluemle

+1

BTW, su estilo de codificación no funcionará si la persona puede ser subclasificada. En su lugar, ponga la lógica en el método "Igual" y haga que el operador "==" llame a Object.Equals (a, b). RAZÓN: que se llama "==" se basa en el tipo COMPILAR-TIEMPO en lugar del tipo DINÁMICO. Object.Equals se despacha dinámicamente. Si se subclasifica a la persona, también es necesario comparar EXACTAMENTE los tipos, no simplemente enviarlos a la persona. Un ejemplo de la correcta codificación de "Iguales" está aquí. http://msdn.microsoft.com/en-us/library/336aedhh(v=vs.85).aspx Observe "GetType()! = obj.GetType())". Esto es esencial cuando existen subclases. – ToolmakerSteve