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.Combinedla tych samych pól, które są używane wEquals. - Należy haszować niezmienne pola, aby klucze pozostały możliwe do znalezienia.
- Kolizje są normalne; metoda
Equalsje 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
- Implementowanie IEquatable
- Przesłanianie GetHashCode
- Implementowanie IComparable
- IComparer do niestandardowego sortowania