0Pricing
C# Academy · Lección

Sobrescritura de GetHashCode

Mantenga coherentes la igualdad y el cálculo de hashes.

Sobrescritura de GetHashCode es una lección gratuita de C# Academy en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de C# Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de C# Academy incluye 4 lecciones en total.

Por qué es importante GetHashCode

Las colecciones basadas en hash, como Dictionary y HashSet, utilizan GetHashCode para asignar los elementos a buckets. Si invalida Equals pero no GetHashCode, estas colecciones pueden no encontrar elementos iguales.

La regla de oro

El contrato es sencillo: si dos objetos son iguales, deben devolver el mismo código hash. No es necesario lo contrario; distintos objetos pueden compartir un código hash (una colisión).

using System;

public struct Point
{
    public int X, Y;
    public Point(int x, int y) { X = x; Y = y; }
    public override bool Equals(object obj) => obj is Point p && p.X == X && p.Y == Y;
    public override int GetHashCode() => HashCode.Combine(X, Y);
}

public class Program
{
    public static void Main()
    {
        var a = new Point(1, 2);
        var b = new Point(1, 2);
        Console.WriteLine(a.Equals(b));
        Console.WriteLine(a.GetHashCode() == b.GetHashCode());
    }
}

Utilice HashCode.Combine

El auxiliar HashCode.Combine combina varios valores de campos en un hash bien distribuido. Es la forma moderna recomendada de implementar GetHashCode.

using System;

public struct Color
{
    public int R, G, B;
    public Color(int r, int g, int b) { R = r; G = g; B = b; }
    public override bool Equals(object obj) => obj is Color c && c.R == R && c.G == G && c.B == B;
    public override int GetHashCode() => HashCode.Combine(R, G, B);
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine(new Color(255, 0, 0).GetHashCode() == new Color(255, 0, 0).GetHashCode());
    }
}

Base la igualdad y el hashing en los mismos datos

Invalide siempre Equals y GetHashCode como pareja, utilizando los mismos campos en ambos. Usar campos distintos hace que objetos iguales produzcan hashes diferentes.

using System;

public class Book
{
    public string Title;
    public int Year;
    public Book(string title, int year) { Title = title; Year = year; }
    public override bool Equals(object obj)
        => obj is Book b && b.Title == Title && b.Year == Year;
    public override int GetHashCode() => HashCode.Combine(Title, Year);
}

public class Program
{
    public static void Main()
    {
        var a = new Book("C#", 2020);
        var b = new Book("C#", 2020);
        Console.WriteLine(a.GetHashCode() == b.GetHashCode());
    }
}

Por qué lo necesitan los diccionarios

Un Dictionary primero calcula el hash de la clave para encontrar un bucket y después utiliza Equals dentro de ese bucket. Un hash incorrecto dirige la búsqueda al bucket equivocado y la clave nunca se encuentra.

using System;
using System.Collections.Generic;

public struct Coord
{
    public int X, Y;
    public Coord(int x, int y) { X = x; Y = y; }
    public override bool Equals(object obj) => obj is Coord c && c.X == X && c.Y == Y;
    public override int GetHashCode() => HashCode.Combine(X, Y);
}

public class Program
{
    public static void Main()
    {
        var map = new Dictionary<Coord, string> { [new Coord(2, 3)] = "hit" };
        Console.WriteLine(map[new Coord(2, 3)]);
    }
}

Calcule el hash a partir de campos inmutables

Los códigos hash deben permanecer estables para un objeto mientras este se encuentre en una colección basada en hash. Base el hash en campos inmutables; si una clave cambia después de insertarla, la colección puede perderle la pista.

using System;

public class Account
{
    public readonly int Id; // immutable, safe to hash
    public string Nickname; // mutable, do not hash
    public Account(int id, string nick) { Id = id; Nickname = nick; }
    public override bool Equals(object obj) => obj is Account a && a.Id == Id;
    public override int GetHashCode() => Id.GetHashCode();
}

public class Program
{
    public static void Main()
    {
        var acc = new Account(42, "old");
        int h1 = acc.GetHashCode();
        acc.Nickname = "new"; // does not change the hash
        Console.WriteLine(h1 == acc.GetHashCode());
    }
}

Gestione los campos nulos

Cuando un campo puede ser nulo, HashCode.Combine lo gestiona de forma segura. Si realiza el cálculo manualmente, controle el valor nulo para evitar una NullReferenceException.

using System;

public class Tag
{
    public string Name;
    public Tag(string name) { Name = name; }
    public override bool Equals(object obj) => obj is Tag t && t.Name == Name;
    public override int GetHashCode() => HashCode.Combine(Name); // null-safe
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine(new Tag(null).GetHashCode() == new Tag(null).GetHashCode());
    }
}

Las colisiones son normales

Los códigos hash tienen 32 bits, por lo que las colisiones son inevitables en conjuntos de datos grandes. Un buen hash simplemente distribuye los valores para mantener escasas las colisiones; Equals resuelve las que se producen.

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        var set = new HashSet<int>();
        for (int i = 0; i < 5; i++) set.Add(i);
        // Equality still works perfectly even though hashing is imperfect in general
        Console.WriteLine(set.Contains(3));
        Console.WriteLine(set.Contains(99));
    }
}

Los registros lo generan automáticamente

Un record genera automáticamente un GetHashCode correcto a partir de sus propiedades, coherente con su Equals basado en valores. Es la forma más sencilla de implementar correctamente el hashing.

using System;
using System.Collections.Generic;

public record Coord(int X, int Y);

public class Program
{
    public static void Main()
    {
        var set = new HashSet<Coord> { new Coord(1, 1) };
        Console.WriteLine(set.Contains(new Coord(1, 1)));
    }
}

Un tipo equatable correcto

En conjunto: los mismos campos en Equals y GetHashCode, claves inmutables y HashCode.Combine para combinar los valores. Este tipo funciona perfectamente como clave de un diccionario.

using System;
using System.Collections.Generic;

public struct ProductKey
{
    public string Sku;
    public int Variant;
    public ProductKey(string sku, int variant) { Sku = sku; Variant = variant; }
    public override bool Equals(object obj)
        => obj is ProductKey k && k.Sku == Sku && k.Variant == Variant;
    public override int GetHashCode() => HashCode.Combine(Sku, Variant);
}

public class Program
{
    public static void Main()
    {
        var stock = new Dictionary<ProductKey, int>
        {
            [new ProductKey("ABC", 1)] = 10
        };
        Console.WriteLine(stock[new ProductKey("ABC", 1)]);
    }
}

Póngalo en práctica

Compruebe que se cumple el contrato: cree dos claves iguales y confirme que coinciden tanto en la igualdad como en sus códigos hash; después, utilícelas en un conjunto.

using System;
using System.Collections.Generic;

public struct Name
{
    public string First, Last;
    public Name(string first, string last) { First = first; Last = last; }
    public override bool Equals(object obj) => obj is Name n && n.First == First && n.Last == Last;
    public override int GetHashCode() => HashCode.Combine(First, Last);
}

public class Program
{
    public static void Main()
    {
        var a = new Name("Ada", "Lovelace");
        var b = new Name("Ada", "Lovelace");
        Console.WriteLine(a.Equals(b) + " " + (a.GetHashCode() == b.GetHashCode()));
        var people = new HashSet<Name> { a };
        Console.WriteLine(people.Contains(b));
    }
}

Comprobación rápida

Recuerde el contrato del hashing.

Resumen

Un hashing correcto mantiene en funcionamiento las colecciones basadas en hash.

  • Los objetos iguales deben devolver el mismo código hash.
  • Utilice HashCode.Combine con los mismos campos que Equals.
  • Calcule el hash a partir de campos inmutables para que las claves sigan siendo localizables.
  • Las colisiones son normales; Equals las resuelve.
  • Los registros generan automáticamente una implementación correcta.

Preguntas frecuentes

¿La lección «Sobrescritura de GetHashCode» es gratis?

Sí — el texto completo de «Sobrescritura de GetHashCode» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de C# Academy, actualiza a CoddyKit PRO. El curso de C# Academy incluye 4 lecciones en total.

¿Qué aprenderé en «Sobrescritura de GetHashCode»?

Mantenga coherentes la igualdad y el cálculo de hashes. Practicas C# Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar C# Academy?

No se requiere experiencia previa. C# Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.

¿Cuánto tiempo toma la lección «Sobrescritura de GetHashCode»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de C# Academy?

Sí. Cada lección de C# Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Implementación de IEquatable
  2. Sobrescritura de GetHashCode
  3. Implementación de IComparable
  4. IComparer para ordenación personalizada
← Volver a C# Academy