Biorąc pod uwagę, że kolekcje takie jak System.Collections.Generic.HashSet<>accept nulljako członek zestawu, można zapytać, jaki nullpowinien być kod skrótu . Wygląda na to, że framework używa 0:
// nullable struct type
int? i = null;
i.GetHashCode(); // gives 0
EqualityComparer<int?>.Default.GetHashCode(i); // gives 0
// class type
CultureInfo c = null;
EqualityComparer<CultureInfo>.Default.GetHashCode(c); // gives 0
Może to być (trochę) problematyczne w przypadku wyliczeń dopuszczających wartość null. Jeśli zdefiniujemy
enum Season
{
Spring,
Summer,
Autumn,
Winter,
}
wtedy Nullable<Season>(również nazywany Season?) może przyjąć tylko pięć wartości, ale dwie z nich, mianowicie nulli Season.Spring, mają ten sam kod skrótu.
Kuszące byłoby napisanie „lepszego” narzędzia do porównywania równości w następujący sposób:
class NewNullEnumEqComp<T> : EqualityComparer<T?> where T : struct
{
public override bool Equals(T? x, T? y)
{
return Default.Equals(x, y);
}
public override int GetHashCode(T? x)
{
return x.HasValue ? Default.GetHashCode(x) : -1;
}
}
Ale czy jest jakiś powód, dla którego nullpowinien być kod skrótu 0?
EDYCJA / DODANIE:
Niektórym wydaje się, że chodzi o nadpisywanie Object.GetHashCode(). Tak naprawdę nie jest. (Jednak autorzy .NET dokonali nadpisania GetHashCode()w Nullable<>strukturze, co jest istotne). Napisana przez użytkownika implementacja parametru GetHashCode()bezparametrowego nigdy nie poradzi sobie z sytuacją, w której znajduje się obiekt, którego szukamy kodu skrótu null.
Chodzi o implementację metody abstrakcyjnej EqualityComparer<T>.GetHashCode(T)lub inną implementację metody interfejsu IEqualityComparer<T>.GetHashCode(T). Teraz, podczas tworzenia tych linków do MSDN, widzę, że jest tam napisane, że te metody rzucają, ArgumentNullExceptionjeśli ich jedynym argumentem jest null. To z pewnością błąd w MSDN? Żadna z własnych implementacji platformy .NET nie zgłasza wyjątków. Rzucenie w takim przypadku skutecznie przerwałoby każdą próbę dodania nulldo HashSet<>. Chyba że HashSet<>robi coś niezwykłego, gdy ma do czynienia z nullprzedmiotem (będę musiał to przetestować).
NOWA EDYCJA / DODATEK:
Teraz próbowałem debugować. Dzięki HashSet<>, mogę potwierdzić, że z comparer domyślny równości, wartości Season.Springi null będzie kończyć się w tym samym segmencie. Można to ustalić, bardzo dokładnie sprawdzając prywatne elementy tablicy m_bucketsi m_slots. Zauważ, że indeksy są zawsze, zgodnie z projektem, przesunięte o jeden.
Kod, który podałem powyżej, jednak tego nie naprawia. Jak się okazuje, HashSet<>nigdy nawet nie zapyta modułu porównującego równość, kiedy wartość wynosi null. To pochodzi z kodu źródłowego HashSet<>:
// Workaround Comparers that throw ArgumentNullException for GetHashCode(null).
private int InternalGetHashCode(T item) {
if (item == null) {
return 0;
}
return m_comparer.GetHashCode(item) & Lower31BitMask;
}
Oznacza to, że przynajmniej w przypadku HashSet<>nie można nawet zmienić skrótu pliku null. Zamiast tego rozwiązaniem jest zmiana skrótu wszystkich innych wartości, na przykład:
class NewerNullEnumEqComp<T> : EqualityComparer<T?> where T : struct
{
public override bool Equals(T? x, T? y)
{
return Default.Equals(x, y);
}
public override int GetHashCode(T? x)
{
return x.HasValue ? 1 + Default.GetHashCode(x) : /* not seen by HashSet: */ 0;
}
}