Dato che le raccolte come System.Collections.Generic.HashSet<>accept nullas a set member, ci si può chiedere quale nulldovrebbe 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 nulle 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 nulldovrebbe 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 ArgumentNullExceptionse 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 nulla HashSet<>. A meno HashSet<>che non faccia qualcosa di straordinario quando si tratta di un nulloggetto (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.Springe null finiranno nello stesso bucket. Ciò può essere determinato esaminando molto attentamente i membri dell'array privato m_bucketse 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;
}
}