GetHashCode y Equals: por qué su redefinición es crítica para C#

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: ese error que ralentiza tu código

\n\n

Cualquiera que haya escrito en C# y se haya topado con Dictionary o HashSet, tarde o temprano se encuentra con esta pregunta en Stack Overflow. Miles de visitas, decenas de respuestas, pero la esencia es una: si ha redefinido Equals en su clase, pero olvidó GetHashCode, su código puede funcionar incorrectamente o ser terriblemente lento. ¿Por qué? Analicémoslo con ejemplos reales.

\n\n

Imagine que está escribiendo un sistema de reserva de billetes de avión. Tiene una clase Flight con un número de vuelo único y una fecha. Quiere usar esta clase como clave en un Dictionary. Parece sencillo. Pero sin un GetHashCode correcto, su diccionario puede convertirse en una lista degenerada con una velocidad de búsqueda O(n) en lugar de O(1).

\n\n

La magia de las tablas hash: cómo funciona Dictionary en C#

\n\n

Para entender el problema, hay que mirar bajo el capó de Dictionary. Internamente, es un arreglo de "cubos" (buckets). Cuando agrega un elemento, C# llama al método GetHashCode() de la clave para determinar el número del cubo. Si el cubo ya tiene elementos, se llama a Equals() para comparar con cada uno de ellos. Es esta combinación la que garantiza una búsqueda ultrarrápida.

\n\n

El contrato que no se debe violar

\n

En C# existe un contrato implícito pero estricto:

\n
    \n
  • Si Equals() devuelve true para dos objetos, sus códigos hash deben ser iguales.
  • \n
  • Si los códigos hash son diferentes, Equals() nunca se llamará (esto es una optimización).
  • \n
  • Lo contrario no es necesario: objetos diferentes pueden tener el mismo código hash (colisión).
  • \n
\n\n

Cuando solo redefine Equals, pero no GetHashCode, hereda la implementación de System.Object, que se basa en la referencia en memoria. Dos objetos "iguales" según su lógica tendrán códigos hash diferentes. Y aquí comienza el infierno.

\n\n

Ejemplo del problema: vuelos perdidos en el diccionario

\n\n

Mire este código. Parece que debería funcionar, pero no lo hace:

\n\n
public class Flight\n{\n    public string FlightNumber { get; }\n    public DateTime Date { get; }\n\n    public Flight(string flightNumber, DateTime date)\n    {\n        FlightNumber = flightNumber;\n        Date = date;\n    }\n\n    public override bool Equals(object obj)\n    {\n        if (obj is Flight other)\n            return FlightNumber == other.FlightNumber && Date == other.Date;\n        return false;\n    }\n    \n    // ¡GetHashCode NO está redefinido!\n}\n\n// Uso\nvar flights = new Dictionary<Flight, string>();\nvar f1 = new Flight("SU100", new DateTime(2024, 12, 25));\nvar f2 = new Flight("SU100", new DateTime(2024, 12, 25));\n\nflights[f1] = "Moscú - París";\n\n// ¡Error! f2 no se encuentra, aunque f1.Equals(f2) == true\nConsole.WriteLine(flights.ContainsKey(f2)); // False
\n\n

¿Por qué sucede esto? f1 y f2 son objetos diferentes en memoria. Tienen códigos hash diferentes de Object.GetHashCode(). El diccionario coloca f1 en el cubo n.º 123 y busca f2 en el cubo n.º 456. ¡Equals ni siquiera se llama! El resultado: el elemento no se encuentra.

\n\n

Problema de rendimiento: cuando todo funciona, pero lento

\n\n

Lo peor es una solución parcial. Supongamos que ha redefinido GetHashCode, pero lo ha hecho mal, por ejemplo, devolviendo una constante:

\n\n
public override int GetHashCode() => 42; // Todos los objetos en un solo cubo
\n\n

Formalmente, el contrato se cumple (objetos iguales tienen el mismo hash). Pero el rendimiento se desploma: todos los objetos caerán en el mismo cubo y el Dictionary degenerará en una lista. La inserción y la búsqueda se convierten en O(n). Para un diccionario con 10 000 elementos, esto es un desastre.

\n\n

¿Cómo redefinir GetHashCode correctamente?

\n\n

Blogs

Book Recommendations