Dato che le raccolte come System.Collections.Generic.HashSet<>
accept null
as a set member, ci si può chiedere quale null
dovrebbe essere il codice hash di . Sembra che il framework utilizzi 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
Questo può essere (un po ') problematico con enumerazioni nullable. Se definiamo
enum Season
{
Spring,
Summer,
Autumn,
Winter,
}
quindi il Nullable<Season>
(chiamato anche Season?
) può assumere solo cinque valori, ma due di essi, ovvero null
e Season.Spring
, hanno lo stesso codice hash.
Si è tentati di scrivere un comparatore di uguaglianza "migliore" come questo:
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;
}
}
Ma c'è qualche motivo per cui null
dovrebbe essere il codice hash 0
?
MODIFICA / AGGIUNTA:
Alcune persone sembrano pensare che si tratti di override Object.GetHashCode()
. In realtà non lo è. (Gli autori di .NET hanno fatto un override di GetHashCode()
nella Nullable<>
struttura che è rilevante, però.) Un'implementazione scritta dall'utente senza parametri GetHashCode()
non può mai gestire la situazione in cui si trova l'oggetto di cui cerchiamo il codice hash null
.
Si tratta di implementare il metodo astratto EqualityComparer<T>.GetHashCode(T)
o altrimenti implementare il metodo di interfaccia IEqualityComparer<T>.GetHashCode(T)
. Ora, durante la creazione di questi collegamenti a MSDN, vedo che lì si dice che questi metodi lanciano un ArgumentNullException
se il loro unico argomento è null
. Questo deve essere certamente un errore su MSDN? Nessuna delle implementazioni di .NET genera eccezioni. Lanciare in quel caso spezzerebbe efficacemente qualsiasi tentativo di aggiungere null
a HashSet<>
. A meno HashSet<>
che non faccia qualcosa di straordinario quando si tratta di un null
oggetto (dovrò testarlo).
NUOVA MODIFICA / AGGIUNTA:
Ora ho provato a eseguire il debug. Con HashSet<>
, posso confermare che con l'operatore di confronto di uguaglianza predefinito, i valori Season.Spring
e null
finiranno nello stesso bucket. Ciò può essere determinato esaminando molto attentamente i membri dell'array privato m_buckets
e m_slots
. Notare che gli indici sono sempre, per impostazione predefinita, compensati di uno.
Il codice che ho fornito sopra, tuttavia, non risolve questo problema. A quanto pare, HashSet<>
non chiederà mai al comparatore di uguaglianza quando il valore è null
. Questo è dal codice sorgente di 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;
}
Ciò significa che, almeno per HashSet<>
, non è nemmeno possibile cambiare l'hash di null
. Invece, una soluzione è cambiare l'hash di tutti gli altri valori, in questo modo:
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;
}
}