Mengingat bahwa koleksi seperti System.Collections.Generic.HashSet<>
menerima null
sebagai anggota set, orang dapat bertanya apa kode hash yang null
seharusnya. Sepertinya kerangka tersebut menggunakan 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
Ini bisa (sedikit) bermasalah dengan enum nullable. Jika kita mendefinisikan
enum Season
{
Spring,
Summer,
Autumn,
Winter,
}
maka Nullable<Season>
(juga disebut Season?
) dapat mengambil hanya lima nilai, tetapi dua di antaranya, yaitu null
dan Season.Spring
, memiliki kode hash yang sama.
Sangat menggoda untuk menulis pembanding kesetaraan yang "lebih baik" seperti ini:
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;
}
}
Tapi adakah alasan mengapa kode hash null
harus 0
?
EDIT / TAMBAH:
Beberapa orang sepertinya berpikir ini tentang menimpa Object.GetHashCode()
. Sebenarnya tidak. (Penulis NET memang membuat override dari GetHashCode()
dalam Nullable<>
struct yang merupakan relevan, meskipun.) Implementasi ditulis pengguna dari parameterless yang GetHashCode()
tidak pernah dapat menangani situasi di mana objek yang kode hash yang kita cari adalah null
.
Ini tentang mengimplementasikan metode abstrak EqualityComparer<T>.GetHashCode(T)
atau mengimplementasikan metode antarmuka IEqualityComparer<T>.GetHashCode(T)
. Sekarang, saat membuat tautan ini ke MSDN, saya melihat bahwa dikatakan di sana bahwa metode ini melempar ArgumentNullException
jika satu-satunya argumen mereka null
. Ini pasti kesalahan di MSDN? Tak satu pun dari implementasi .NET sendiri yang memunculkan pengecualian. Melemparkan dalam kasus itu secara efektif akan mematahkan setiap upaya untuk menambah null
ke HashSet<>
. Kecuali HashSet<>
melakukan sesuatu yang luar biasa ketika berhadapan dengan suatu null
item (saya harus mengujinya).
EDIT / TAMBAHAN BARU:
Sekarang saya mencoba debugging. Dengan HashSet<>
, saya dapat mengonfirmasi bahwa dengan pembanding kesetaraan default, nilai Season.Spring
dan null
akan berakhir di keranjang yang sama. Ini dapat ditentukan dengan sangat hati-hati memeriksa anggota array pribadi m_buckets
dan m_slots
. Perhatikan bahwa indeks selalu, menurut desain, diimbangi satu.
Kode yang saya berikan di atas tidak, bagaimanapun, memperbaiki ini. Ternyata, HashSet<>
bahkan tidak akan pernah meminta pembanding kesetaraan ketika nilainya null
. Ini dari kode sumber 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;
}
Artinya, setidaknya untuk HashSet<>
, bahkan tidak mungkin untuk mengubah hash null
. Sebagai gantinya, solusinya adalah mengubah hash dari semua nilai lainnya, seperti ini:
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;
}
}