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;
}
}
Jawaban:
Selama kode hash yang dikembalikan untuk null konsisten dengan jenisnya, Anda akan baik-baik saja. Satu-satunya persyaratan untuk kode hash adalah bahwa dua objek yang dianggap sama memiliki kode hash yang sama.
Mengembalikan 0 atau -1 untuk null, selama Anda memilih satu dan mengembalikannya sepanjang waktu, akan berhasil. Jelas, kode hash non-null tidak boleh mengembalikan nilai apa pun yang Anda gunakan untuk null.
Pertanyaan serupa:GetHashCode pada bidang null?
Apa yang harus dikembalikan GetHashCode ketika pengenal objek bernilai null?
"Keterangan" dari entri MSDN ini menjelaskan lebih detail seputar kode hash. Sayangnya, dokumentasi tidak memberikan liputan atau diskusi tentang nilai nol sama sekali - bahkan dalam konten komunitas.Untuk mengatasi masalah Anda dengan enum, terapkan kembali kode hash untuk mengembalikan bukan nol, tambahkan entri enum default "tidak diketahui" yang setara dengan null, atau jangan gunakan enum nullable.
Ngomong-ngomong, temuan menarik.
Masalah lain yang saya lihat dengan ini umumnya adalah bahwa kode hash tidak dapat mewakili tipe 4 byte atau lebih besar yang nullable tanpa setidaknya satu tabrakan (lebih banyak karena ukuran tipe meningkat). Misalnya, kode hash dari sebuah int hanyalah int, jadi kode ini menggunakan rentang int lengkap. Nilai apa dalam rentang itu yang Anda pilih untuk null? Apa pun yang Anda pilih akan bertabrakan dengan kode hash nilai itu sendiri.
Tabrakan dalam dan dari dirinya sendiri tidak selalu menjadi masalah, tetapi Anda perlu tahu bahwa benturan itu ada. Kode hash hanya digunakan dalam beberapa keadaan. Seperti yang dinyatakan dalam dokumen di MSDN, kode hash tidak dijamin untuk mengembalikan nilai yang berbeda untuk objek yang berbeda sehingga seharusnya tidak diharapkan.
sumber
Object.GetHashCode()
di kelas Anda sendiri (atau struct), Anda tahu bahwa kode ini hanya akan dipukul ketika orang benar-benar memiliki instance dari kelas Anda. Contoh itu tidak mungkinnull
. Itulah mengapa Anda tidak memulai penggantianObject.GetHashCode()
denganif (this == null) return -1;
Ada perbedaan antara "menjadinull
" dan "menjadi objek yang memiliki beberapa bidang yang adanull
".T
, maka(T?)null
dan(T?)default(T)
akan memiliki kode hash yang sama (dalam implementasi .NET saat ini). Yang bisa diubah jika pelaksana dari NET berubah baik kode hash darinull
atau algoritma kode hash dariSystem.Enum
.HashSet<>
) itu tidak berfungsi untuk mengubah kode hashnull
.Ingatlah bahwa kode hash digunakan sebagai langkah pertama dalam menentukan persamaan saja, dan [is / should] tidak pernah digunakan sebagai penentuan de-facto apakah dua objek sama.
Jika kode hash dua objek tidak sama maka mereka diperlakukan sebagai tidak sama (karena kami berasumsi bahwa implementasi yang tepat adalah benar - yaitu kami tidak menebak-nebak). Jika mereka memiliki kode hash yang sama, maka mereka kemudian harus diperiksa untuk persamaan aktual yang, dalam kasus Anda,
null
dan nilai enum akan gagal.Akibatnya - menggunakan nol sama baiknya dengan nilai lain dalam kasus umum.
Tentu, akan ada situasi, seperti enum Anda, di mana nol ini dibagikan dengan kode hash nilai nyata . Pertanyaannya adalah apakah, bagi Anda, overhead yang sangat kecil dari perbandingan tambahan menyebabkan masalah.
Jika demikian, maka tentukan pembanding Anda sendiri untuk kasus nullable untuk tipe tertentu Anda, dan pastikan bahwa nilai null selalu menghasilkan kode hash yang selalu sama (tentu saja!) Dan nilai yang tidak dapat dihasilkan oleh yang mendasarinya tipe algoritma kode hash sendiri. Untuk tipe Anda sendiri, ini bisa dilakukan. Untuk orang lain - semoga berhasil :)
sumber
Tidak harus menjadi nol - Anda bisa membuat 42 jika Anda ingin.
Yang terpenting adalah konsistensi selama pelaksanaan program.
Itu hanya representasi yang paling jelas, karena
null
sering direpresentasikan sebagai nol secara internal. Artinya, saat men-debug, jika Anda melihat kode hash nol, Anda mungkin akan berpikir, "Hmm .. apakah ini masalah referensi null?"Perhatikan bahwa jika Anda menggunakan nomor seperti
0xDEADBEEF
, maka seseorang dapat mengatakan Anda menggunakan nomor ajaib ... dan Anda seperti itu. (Anda bisa mengatakan nol adalah angka ajaib juga, dan Anda akan benar ... kecuali bahwa itu digunakan secara luas sehingga menjadi semacam pengecualian pada aturan tersebut.)sumber
Pertanyaan bagus.
Saya baru saja mencoba membuat kode ini:
enum Season { Spring, Summer, Autumn, Winter, }
dan lakukan ini seperti ini:
Season? v = null; Console.WriteLine(v);
itu kembali
null
jika saya lakukan, bukannya normal
Season? v = Season.Spring; Console.WriteLine((int)v);
itu kembali
0
, seperti yang diharapkan, atau Spring sederhana jika kita menghindari transmisi keint
.Jadi .. jika Anda melakukan hal berikut:
Season? v = Season.Spring; Season? vnull = null; if(vnull == v) // never TRUE
EDIT
Dari MSDN
Jika dua objek dibandingkan sebagai sama, metode GetHashCode untuk setiap objek harus mengembalikan nilai yang sama. Namun, jika dua objek tidak dibandingkan sebagai sama, metode GetHashCode untuk dua objek tidak harus mengembalikan nilai yang berbeda.
Dengan kata lain: jika dua objek memiliki kode hash yang sama namun tidak berarti keduanya sama, penyebab persamaan nyata ditentukan oleh Equals .
Dari MSDN lagi:
sumber
Bisa jadi apa saja. Saya cenderung setuju bahwa 0 belum tentu pilihan terbaik, tetapi itu mungkin menyebabkan bug paling sedikit.
Fungsi hash mutlak harus mengembalikan hash yang sama untuk nilai yang sama. Setelah terdapat sebuah komponen yang melakukan ini, ini benar-benar nilai hanya berlaku untuk hash dari
null
. Jika ada konstanta untuk ini, seperti, hmobject.HashOfNull
, maka seseorang yang mengimplementasikanIEqualityComparer
harus tahu untuk menggunakan nilai itu. Jika mereka tidak memikirkannya, kemungkinan mereka akan menggunakan 0 sedikit lebih tinggi daripada setiap nilai lainnya, saya rasa.Seperti disebutkan di atas, saya pikir itu sama sekali tidak mungkin berhenti penuh, hanya karena ada jenis yang sudah mengikuti konvensi yang hash nol adalah 0.
sumber
EqualityComparer<T>.GetHashCode(T)
untuk beberapa tipe tertentuT
yang memungkinkannull
, dia harus melakukan sesuatu saat argumennyanull
. Anda bisa (1) melemparArgumentNullException
, (2) mengembalikan0
, atau (3) mengembalikan sesuatu yang lain. Saya mengambil jawaban Anda untuk rekomendasi selalu kembali0
dalam situasi itu?Ini adalah 0 demi kesederhanaan. Tidak ada persyaratan yang begitu sulit. Anda hanya perlu memastikan persyaratan umum pengkodean hash.
Misalnya, Anda perlu memastikan bahwa jika dua objek sama, kode hashnya juga harus sama. Oleh karena itu, kode hash yang berbeda harus selalu mewakili objek yang berbeda (tetapi belum tentu benar, sebaliknya: dua objek yang berbeda mungkin memiliki kode hash yang sama, meskipun jika ini sering terjadi maka ini bukan fungsi hash berkualitas baik - tidak memiliki ketahanan tabrakan yang baik).
Tentu saja, saya membatasi jawaban saya untuk persyaratan alam matematika. Ada juga kondisi teknis khusus .NET, yang dapat Anda baca di sini . 0 untuk nilai nol tidak ada di antara mereka.
sumber
Jadi ini bisa dihindari dengan menggunakan
Unknown
nilai enum (meskipun tampaknya agak aneh untukSeason
tidak diketahui). Jadi hal seperti ini akan meniadakan masalah ini:public enum Season { Unknown = 0, Spring, Summer, Autumn, Winter } Season some_season = Season.Unknown; int code = some_season.GetHashCode(); // 0 some_season = Season.Autumn; code = some_season.GetHashCode(); // 3
Kemudian Anda akan memiliki nilai kode hash unik untuk setiap musim.
sumber
Secara pribadi saya merasa menggunakan nilai nullable agak canggung dan mencoba menghindarinya kapan pun saya bisa. Masalah Anda hanyalah alasan lain. Kadang-kadang mereka sangat berguna tetapi aturan praktis saya adalah tidak mencampur tipe nilai dengan null jika memungkinkan hanya karena ini berasal dari dua dunia yang berbeda. Dalam kerangka .NET mereka tampaknya melakukan hal yang sama - banyak tipe nilai menyediakan
TryParse
metode yang merupakan cara untuk memisahkan nilai dari tidak ada nilai (null
).Dalam kasus khusus Anda, mudah untuk menyingkirkan masalah karena Anda menangani
Season
tipe Anda sendiri .(Season?)null
bagi saya berarti 'musim tidak ditentukan' seperti ketika Anda memiliki formulir web di mana beberapa bidang tidak diperlukan. Menurut pendapat saya, lebih baik untuk menetapkan 'nilai' khususenum
itu sendiri daripada menggunakan sedikit kikukNullable<T>
. Ini akan lebih cepat (tanpa tinju) lebih mudah dibaca (Season.NotSpecified
vsnull
) dan akan menyelesaikan masalah Anda dengan kode hash.Tentu saja untuk jenis lain, seperti
int
Anda tidak dapat memperluas domain nilai dan untuk menandakan salah satu nilai sebagai spesial tidak selalu memungkinkan. Tetapi denganint?
tabrakan kode hash adalah masalah yang jauh lebih kecil, jika sama sekali.sumber
Nullable<>
struct (di manaHasValue
anggota akan diatur ketrue
). Apakah Anda yakin masalahnya benar-benar lebih kecilint?
? Sering kali seseorang hanya menggunakan sedikit nilaiint
, dan kemudian itu setara dengan enum (yang secara teori dapat memiliki banyak anggota).int
lebih masuk akal. Tentu saja preferensinya berbeda-beda.Tuple.Create( (object) null! ).GetHashCode() // 0 Tuple.Create( 0 ).GetHashCode() // 0 Tuple.Create( 1 ).GetHashCode() // 1 Tuple.Create( 2 ).GetHashCode() // 2
sumber