what visual studio c# warnings equals equals-operator

visual - string hashcode c#



¿Qué hay de malo en definir el operador== pero no en definir Equals() o GetHashCode()? (8)

Existe una expectativa general dentro del Marco de que ciertas operaciones siempre deben producir el mismo resultado. La razón es que ciertas operaciones (en particular, la clasificación y la búsqueda, que constituyen una gran parte de cualquier aplicación) dependen de estas diferentes operaciones que producen resultados significativos y consistentes. En este caso, está rompiendo un par de esas suposiciones:

  • Si hay una operación válida == entre a y b , debería producir el mismo resultado que a.Equals(b)
  • Similar, si hay una operación válida != Entre a y b , debería producir el mismo resultado que !a.Equals(b)
  • Si existen dos objetos a y b , para los cuales a == b , entonces a y b deben producir la misma clave cuando se almacenan en una tabla hash.

Los dos primeros, IMO, son obvios; Si está definiendo lo que significa que dos objetos sean iguales, debe incluir todas las formas en que puede verificar que dos objetos sean iguales. Tenga en cuenta que el compilador no (en general, no puede ) exigir que realmente siga esas reglas. No va a realizar un análisis de código complejo del cuerpo de sus operadores para ver si ya imitan a Equals porque, en el peor de los casos, eso podría ser equivalente a resolver el problema de la detención.

Sin embargo, lo que puede hacer es verificar los casos en los que es más probable que rompa esas reglas, en particular, proporcionó operadores de comparación personalizados y no proporcionó un método personalizado de Equals . El supuesto aquí es que no se habría molestado en proporcionar operadores si no quisiera que hicieran algo especial, en cuyo caso, debería haber proporcionado un comportamiento personalizado para todos los métodos que deben estar sincronizados.

Si implementara Equals para ser algo diferente de == el compilador no se quejaría; Habría llegado al límite de lo difícil que C # está dispuesto a tratar de evitar que haga algo estúpido. Estaba dispuesto a impedir que introdujeras errores sutiles accidentalmente en tu código, pero te permitirá hacerlo a propósito si eso es lo que quieres.

El tercer supuesto tiene que ver con el hecho de que muchas operaciones internas en el Marco utilizan alguna variante de una tabla hash. Si tengo dos objetos que, según mi definición, son "iguales", debería poder hacer esto:

if (a == b) { var tbl = new HashTable(); tbl.Add(a, "Test"); var s = tbl[b]; Debug.Assert(s.Equals("Test")); }

Esta es una propiedad básica de las tablas hash que causaría problemas muy extraños si de repente no fuera cierto.

Para el código de abajo

public struct Person { public int ID; public static bool operator ==(Person a, Person b) { return a.Equals(b); } public static bool operator !=(Person a, Person b) { return !a.Equals(b); } }

¿Por qué el compilador me da estas advertencias?
¿Qué hay de malo en no definir los métodos a continuación?

warning CS0660: ''Person'' defines operator == or operator != but does not override Object.Equals(object o) warning CS0661: ''Person'' defines operator == or operator != but does not override Object.GetHashCode()


Lea las páginas de MSDN.

CS0660

CS0661

El compilador básicamente dice: "Ya que estás diciendo que sabes cómo comparar tu objeto, debes hacerlo de esta manera todo el tiempo".


Probablemente porque no se espera que el método predeterminado Equals() sea ​​lo suficientemente bueno para un sistema real (por ejemplo, en su clase debería comparar el campo de ID ).


Si anula Equals y GetHashCode , ni siquiera tendría que anular a los operadores, y eso es un enfoque más limpio. Editado: debería funcionar ya que esta es una estructura.


Supongo que está recibiendo estas advertencias porque el compilador no sabe que usa el método Equals in ==

Supongamos que tiene esta implementación

public struct Person { public int ID; public static bool operator ==(Person a, Person b) { return Math.Abs(a.ID - b.ID) <= 5; } public static bool operator !=(Person a, Person b) { return Math.Abs(a.ID - b.ID) > 5; } }

Entonces

Person p1 = new Person() { ID = 1 }; Person p2 = new Person() { ID = 4 }; bool b1 = p1 == p2; bool b2 = p1.Equals(p2);

b1 sería cierto , pero b2 falso

--EDITAR--

Ahora supongamos que quieres hacer esto

Dictionary<Person, Person> dict = new Dictionary<Person, Person>(); dict.Add(p1, p1); var x1 = dict[p2]; //Since p2 is supposed to be equal to p1 (according to `==`), this should return p1

Pero esto lanzaría una excepción algo como KeyNotFound

Pero si añades

public override bool Equals(object obj) { return Math.Abs(ID - ((Person)obj).ID) <= 5; } public override int GetHashCode() { return 0; }

obtendrás lo que quieras

El compilador simplemente te advierte que puedes enfrentarte con condiciones similares


Todo lo que necesita hacer es agregar otro miembro a su estructura, por ejemplo, Nombre.

Entonces, si tiene dos personas con una ID de 63 pero diferentes nombres, ¿son iguales o no?

Todo depende de qué definición de "igual" quieres implementar.

Use una estructura de ejemplo mejor, escriba una aplicación de noddy para ejecutar los diversos métodos y vea qué sucede cuando cambia las definiciones de igualdad y / o equivalencia, si no están todas al mismo nivel, terminará con cosas como: (a == b)! = (a! = b), lo que puede ser cierto, pero si no anula todos los métodos, cualquiera que use el código se preguntará cuál fue su intención.

Básicamente, el compilador le está diciendo que sea un buen ciudadano y que aclare su intención.


EDITAR : Esta respuesta se ha corregido, entre otras cosas, para tener en cuenta que los tipos de valores definidos por el usuario no generan == , y para mencionar los problemas de rendimiento con ValueType.Equals .

En general, anular uno, pero no todo, es confuso. El usuario espera que ninguno de los dos se invalide, o que ambos lo estén, con la misma semántica.

recommendations de Microsoft para este estado (entre otras cosas):

  • Implemente el método GetHashCode siempre que implemente el método Equals. Esto mantiene a Equals y GetHashCode sincronizados.

  • Reemplace el método Equals siempre que implemente el operador de igualdad (==), y haga que hagan lo mismo.

En su caso, tiene una razón válida para diferir a Equals (el compilador no implementa automáticamente == ) y anular solo esos dos ( == / != ). Sin embargo, todavía hay un problema de rendimiento, ya que ValueType.Equals usa la reflexión:

"Anule el método de Igualdad para un tipo en particular para mejorar el rendimiento del método y representar más estrechamente el concepto de igualdad para el tipo".

Por lo tanto, todavía se recomienda anular todos ( == / != / Equals ) al final. Por supuesto, el rendimiento puede no importar para esta estructura trivial.


public struct Coord { public int x; public int y; public Coord(int x, int y) { this.x = x; this.y = y; } public static bool operator ==(Coord c1, Coord c2) { return c1.x == c2.x && c1.y == c2.y; } public static bool operator !=(Coord c1, Coord c2) { return !(c1 == c2); } public bool Equals(Coord other) { return x == other.x && y == other.y; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; return obj is Coord && Equals((Coord) obj); } public override int GetHashCode() { return 0; } }

Aquí hay un ejemplo. Con suerte, es útil.