0Pricing
C# Academy · Lekcja

Przesłanianie GetHashCode

Zachowuj spójność równości i funkcji skrótu.

Przesłanianie GetHashCode to bezpłatna lekcja C# Academy na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej C# Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs C# Academy zawiera 4 lekcji w sumie.

Dlaczego GetHashCode ma znaczenie

Kolekcje oparte na haszowaniu, takie jak Dictionary i HashSet, używają GetHashCode do umieszczania elementów w kubełkach. Jeśli przesłonięto Equals, ale nie GetHashCode, kolekcje te mogą nie znaleźć równych elementów.

Złota zasada

Zasada jest prosta: jeśli dwa obiekty są równe, muszą zwracać ten sam kod haszujący. Odwrotność nie jest wymagana; różne obiekty mogą mieć ten sam kod haszujący (kolizja).

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());
    }
}

Używaj HashCode.Combine

Pomocnicza metoda HashCode.Combine łączy wartości kilku pól w dobrze rozłożony kod haszujący. Jest zalecanym współczesnym sposobem implementowania 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());
    }
}

Implementuj równość i haszowanie razem

Zawsze należy przesłaniać Equals i GetHashCode jako parę, używając w obu metodach tych samych pól. Użycie różnych pól sprawia, że równe obiekty otrzymują różne kody haszujące.

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());
    }
}

Dlaczego Dictionary tego potrzebuje

Dictionary najpierw oblicza kod haszujący klucza, aby znaleźć kubełek, a następnie używa Equals w jego obrębie. Nieprawidłowy kod haszujący kieruje wyszukiwanie do niewłaściwego kubełka, przez co klucz nigdy nie zostaje znaleziony.

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)]);
    }
}

Haszowanie niezmiennych pól

Kody haszujące powinny pozostać stabilne dla obiektu przez cały czas jego przechowywania w kolekcji opartej na haszowaniu. Kod należy oprzeć na niezmiennych polach; jeśli klucz zmieni się po dodaniu, kolekcja może go zgubić.

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());
    }
}

Obsługa pól null

Jeśli pole może mieć wartość null, HashCode.Combine bezpiecznie ją obsłuży. Przy ręcznym obliczaniu kodu należy sprawdzić wartość null, aby uniknąć wyjątku 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());
    }
}

Kolizje są normalne

Kody haszujące mają 32 bity, dlatego w przypadku dużych zbiorów danych kolizje są nieuniknione. Dobry kod haszujący jedynie równomiernie rozkłada wartości, aby kolizje występowały rzadko; metoda Equals rozstrzyga te, do których dojdzie.

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));
    }
}

Rekordy generują go automatycznie

Element record automatycznie generuje poprawny GetHashCode na podstawie swoich właściwości, zgodny z opartą na wartościach metodą Equals. To najprostszy sposób na poprawne haszowanie.

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)));
    }
}

Poprawnie zaimplementowany typ z równością

Wszystko razem oznacza: te same pola w metodach Equals i GetHashCode, niezmienne klucze oraz użycie HashCode.Combine do ich połączenia. Taki typ działa bezbłędnie jako klucz słownika.

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)]);
    }
}

Wypróbuj samodzielnie

Należy sprawdzić spełnienie zasady: utworzyć dwa równe klucze i potwierdzić, że zarówno ich równość, jak i kody haszujące są zgodne, a następnie użyć ich w zbiorze.

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));
    }
}

Szybkie sprawdzenie

Należy przypomnieć sobie zasadę haszowania.

Podsumowanie

Poprawne haszowanie zapewnia prawidłowe działanie kolekcji opartych na haszowaniu.

  • Równe obiekty muszą zwracać ten sam kod haszujący.
  • Należy używać HashCode.Combine dla tych samych pól, które są używane w Equals.
  • Należy haszować niezmienne pola, aby klucze pozostały możliwe do znalezienia.
  • Kolizje są normalne; metoda Equals je rozstrzyga.
  • Rekordy automatycznie generują poprawną implementację.

Często zadawane pytania

Czy lekcja „Przesłanianie GetHashCode” jest bezpłatna?

Tak — pełny tekst „Przesłanianie GetHashCode” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu C# Academy, przejdź na CoddyKit PRO. Kurs C# Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Przesłanianie GetHashCode”?

Zachowuj spójność równości i funkcji skrótu. Ćwiczysz C# Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć C# Academy?

Nie wymagamy żadnego doświadczenia. C# Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.

Ile czasu zajmuje lekcja „Przesłanianie GetHashCode”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji C# Academy?

Tak. Każda lekcja C# Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Implementowanie IEquatable
  2. Przesłanianie GetHashCode
  3. Implementowanie IComparable
  4. IComparer do niestandardowego sortowania
← Powrót do C# Academy