Diberi kelas berikut
public class Foo
{
public int FooId { get; set; }
public string FooName { get; set; }
public override bool Equals(object obj)
{
Foo fooItem = obj as Foo;
if (fooItem == null)
{
return false;
}
return fooItem.FooId == this.FooId;
}
public override int GetHashCode()
{
// Which is preferred?
return base.GetHashCode();
//return this.FooId.GetHashCode();
}
}
Saya telah mengganti Equals
metode karena Foo
mewakili baris untuk Foo
tabel s. Yang merupakan metode yang disukai untuk menimpaGetHashCode
?
Mengapa penting untuk mengganti GetHashCode
?
c#
overriding
hashcode
David Basarab
sumber
sumber
Jawaban:
Ya, penting jika item Anda akan digunakan sebagai kunci dalam kamus, atau
HashSet<T>
, dll - karena ini digunakan (tanpa adanya kebiasaanIEqualityComparer<T>
) untuk mengelompokkan item ke dalam ember. Jika kode hash untuk dua item tidak cocok, mereka mungkin tidak pernah dianggap sama ( Persamaan tidak akan pernah disebut).Metode GetHashCode () harus mencerminkan
Equals
logika; aturannya adalah:Equals(...) == true
) maka mereka harus mengembalikan nilai yang sama untukGetHashCode()
GetHashCode()
sama, tidak perlu bagi mereka untuk menjadi sama; ini adalah tabrakan, danEquals
akan dipanggil untuk melihat apakah itu persamaan nyata atau tidak.Dalam hal ini, sepertinya "
return FooId;
" merupakanGetHashCode()
implementasi yang sesuai . Jika Anda menguji beberapa properti, adalah umum untuk menggabungkannya menggunakan kode seperti di bawah ini, untuk mengurangi tabrakan diagonal (yaitu sehingganew Foo(3,5)
memiliki kode hash yang berbedanew Foo(5,3)
):Oh - untuk kenyamanan, Anda mungkin juga mempertimbangkan penyediaan
==
dan!=
operator saat menggantiEquals
danGetHashCode
.Peragaan tentang apa yang terjadi ketika Anda melakukan kesalahan ini ada di sini .
sumber
Ini sebenarnya sangat sulit untuk diimplementasikan
GetHashCode()
dengan benar karena, di samping aturan yang telah disebutkan Marc, kode hash tidak boleh berubah selama masa objek. Oleh karena itu bidang yang digunakan untuk menghitung kode hash harus tidak berubah.Saya akhirnya menemukan solusi untuk masalah ini ketika saya bekerja dengan NHibernate. Pendekatan saya adalah menghitung kode hash dari ID objek. ID hanya dapat ditetapkan melalui konstruktor jadi jika Anda ingin mengubah ID, yang sangat tidak mungkin, Anda harus membuat objek baru yang memiliki ID baru dan karenanya kode hash baru. Pendekatan ini bekerja paling baik dengan GUID karena Anda dapat memberikan konstruktor tanpa parameter yang secara acak menghasilkan ID.
sumber
Dengan mengesampingkan Sama Anda pada dasarnya menyatakan bahwa Anda adalah orang yang lebih tahu bagaimana membandingkan dua contoh dari jenis yang diberikan, sehingga Anda cenderung menjadi kandidat terbaik untuk memberikan kode hash terbaik.
Ini adalah contoh bagaimana ReSharper menulis fungsi GetHashCode () untuk Anda:
Seperti yang Anda lihat, coba tebak kode hash yang baik berdasarkan semua bidang di kelas, tetapi karena Anda tahu domain objek atau rentang nilai, Anda masih bisa memberikan yang lebih baik.
sumber
0 ^ a = a
, jadi0 ^ m_someVar1 = m_someVar1
. Dia mungkin juga mengatur nilai awalresult
untukm_someVar1
.Tolong jangan lupa untuk memeriksa parameter obj
null
saat menimpaEquals()
. Dan juga membandingkan tipenya.Alasan untuk ini adalah:
Equals
harus mengembalikan false jika dibandingkan dengannull
. Lihat juga http://msdn.microsoft.com/en-us/library/bsc2ak47.aspxsumber
obj
memang sama denganthis
tidak peduli berapa Equals () dari baseclass dipanggil.fooItem
ke atas dan kemudian memeriksa untuk null akan berkinerja lebih baik dalam kasus null atau tipe yang salah.obj as Foo
tidak valid.Bagaimana tentang:
sumber
string.Format
. Salah satu culun lain yang saya lihat adalahnew { prop1, prop2, prop3 }.GetHashCode()
. Tidak bisa berkomentar mana yang lebih lambat di antara keduanya. Jangan menyalahgunakan alat.{ prop1="_X", prop2="Y", prop3="Z" }
dan{ prop1="", prop2="X_Y", prop3="Z_" }
. Anda mungkin tidak menginginkan itu.Kami memiliki dua masalah untuk diatasi.
Anda tidak dapat memberikan yang masuk akal
GetHashCode()
jika bidang apa pun di objek dapat diubah. Juga sering suatu objek tidak akan pernah digunakan dalam koleksi yang bergantung padaGetHashCode()
. Jadi biaya implementasiGetHashCode()
sering tidak sepadan, atau tidak mungkin.Jika seseorang meletakkan objek Anda dalam koleksi yang memanggil
GetHashCode()
dan Anda telah menimpanyaEquals()
tanpa juga membuatGetHashCode()
perilaku dengan cara yang benar, orang itu mungkin menghabiskan waktu berhari-hari melacak masalah.Karena itu secara default saya lakukan.
sumber
GetHashCode
fungsi sedemikian rupa sehingga setiap dua objek yang sama mengembalikan kode hash yang sama;return 24601;
danreturn 8675309;
keduanya akan menjadi implementasi yang valid dariGetHashCode
. PerformaDictionary
hanya akan layak ketika jumlah item kecil, dan akan menjadi sangat buruk jika jumlah item menjadi besar, tetapi itu akan berfungsi dengan benar dalam hal apa pun.Itu karena kerangka kerja mengharuskan dua objek yang sama harus memiliki kode hash yang sama. Jika Anda mengganti metode equals untuk melakukan perbandingan khusus dua objek dan kedua objek dianggap sama dengan metode tersebut, maka kode hash dari kedua objek juga harus sama. (Kamus dan Hashtable mengandalkan prinsip ini).
sumber
Tambahkan saja jawaban di atas:
Jika Anda tidak menimpa Sama dengan maka perilaku default adalah bahwa referensi objek dibandingkan. Hal yang sama berlaku untuk kode hash - penerapan standar biasanya didasarkan pada alamat memori referensi. Karena Anda mengganti Equals, artinya perilaku yang benar adalah membandingkan apa pun yang Anda implementasikan pada Equals dan bukan referensi, jadi Anda harus melakukan hal yang sama untuk kode hash.
Klien kelas Anda akan mengharapkan kode hash memiliki logika yang mirip dengan metode equals, misalnya metode LINQ yang menggunakan IEqualityComparer pertama membandingkan kode hash dan hanya jika mereka sama mereka akan membandingkan metode Equals () yang mungkin lebih mahal untuk menjalankan, jika kita tidak mengimplementasikan kode hash, objek yang sama mungkin akan memiliki kode hash yang berbeda (karena mereka memiliki alamat memori yang berbeda) dan akan ditentukan secara salah karena tidak sama (Equals () bahkan tidak akan mengenai).
Selain itu, kecuali masalah yang Anda mungkin tidak dapat menemukan objek Anda jika Anda menggunakannya dalam kamus (karena dimasukkan oleh satu kode hash dan ketika Anda mencarinya, kode hash default mungkin akan berbeda dan lagi sama dengan Equals () bahkan tidak akan dipanggil, seperti yang dijelaskan Marc Gravell dalam jawabannya, Anda juga memperkenalkan pelanggaran kamus atau konsep hashset yang seharusnya tidak mengizinkan kunci identik - Anda sudah menyatakan bahwa benda-benda itu pada dasarnya sama ketika Anda mengesampingkan Persamaan sehingga Anda tidak tidak ingin keduanya sebagai kunci yang berbeda pada struktur data yang dianggap memiliki kunci unik. Tetapi karena mereka memiliki kode hash yang berbeda, kunci "yang sama" akan dimasukkan sebagai yang berbeda.
sumber
Kode Hash digunakan untuk koleksi berbasis hash seperti Kamus, Hashtable, HashSet dll. Tujuan kode ini adalah untuk dengan cepat mengurutkan objek tertentu dengan memasukkannya ke dalam grup tertentu (bucket). Pre-sorting ini sangat membantu dalam menemukan objek ini ketika Anda perlu mengambilnya kembali dari koleksi hash karena kode harus mencari objek Anda hanya dalam satu ember, bukan di semua objek yang dikandungnya. Distribusi kode hash yang lebih baik (keunikan yang lebih baik) semakin cepat pengambilan. Dalam situasi ideal di mana setiap objek memiliki kode hash unik, menemukan itu adalah operasi O (1). Dalam kebanyakan kasus mendekati O (1).
sumber
Itu belum tentu penting; itu tergantung pada ukuran koleksi Anda dan persyaratan kinerja Anda dan apakah kelas Anda akan digunakan di perpustakaan di mana Anda mungkin tidak tahu persyaratan kinerja. Saya sering tahu ukuran koleksi saya tidak terlalu besar dan waktu saya lebih berharga daripada beberapa mikrodetik kinerja yang diperoleh dengan membuat kode hash yang sempurna; jadi (untuk menghilangkan peringatan yang mengganggu oleh kompiler) Saya cukup menggunakan:
(Tentu saja saya bisa menggunakan #pragma untuk mematikan peringatan juga tapi saya lebih suka cara ini.)
Ketika Anda berada dalam posisi yang Anda lakukan memerlukan kinerja dari semua masalah yang disebutkan oleh orang lain di sini berlaku, tentu saja. Paling penting - jika tidak, Anda akan mendapatkan hasil yang salah saat mengambil item dari kumpulan atau kamus hash: kode hash tidak boleh berbeda dengan masa hidup suatu objek (lebih akurat, selama waktu ketika kode hash diperlukan, seperti saat sedang kunci dalam kamus): misalnya, berikut ini salah karena Nilai bersifat publik sehingga dapat diubah secara eksternal ke kelas selama masa pakai instance, jadi Anda tidak boleh menggunakannya sebagai dasar untuk kode hash:
Di sisi lain, jika Nilai tidak dapat diubah, boleh saja menggunakan:
sumber
Anda harus selalu menjamin bahwa jika dua objek sama, seperti yang didefinisikan oleh Equals (), mereka harus mengembalikan kode hash yang sama. Seperti yang dinyatakan oleh beberapa komentar lainnya, secara teori ini tidak wajib jika objek tidak akan pernah digunakan dalam wadah berbasis hash seperti HashSet atau Kamus. Saya menyarankan Anda untuk selalu mengikuti aturan ini. Alasannya hanya karena terlalu mudah bagi seseorang untuk mengubah koleksi dari satu jenis ke yang lain dengan maksud yang baik untuk benar-benar meningkatkan kinerja atau hanya menyampaikan kode semantik dengan cara yang lebih baik.
Sebagai contoh, misalkan kita menyimpan beberapa objek dalam Daftar. Beberapa saat kemudian seseorang benar-benar menyadari bahwa HashSet adalah alternatif yang jauh lebih baik karena karakteristik pencarian yang lebih baik misalnya. Inilah saatnya kita bisa mendapat masalah. Daftar akan secara internal menggunakan pembanding kesetaraan default untuk jenis yang berarti Setara dalam kasus Anda sementara HashSet memanfaatkan GetHashCode (). Jika keduanya berperilaku berbeda, demikian juga program Anda. Dan ingatlah bahwa masalah seperti itu bukan yang paling mudah untuk dipecahkan.
Saya telah merangkum perilaku ini dengan beberapa perangkap GetHashCode () lainnya di a posting blog di mana Anda dapat menemukan contoh dan penjelasan lebih lanjut.
sumber
Pada
.NET 4.7
metode pengesampingan yang disukaiGetHashCode()
ditunjukkan di bawah ini. Jika menargetkan versi .NET yang lebih lama, sertakan paket nuget System.ValueTuple .Dalam hal kinerja, metode ini akan mengungguli sebagian besar implementasi kode hash komposit . The ValueTuple adalah
struct
sehingga tidak akan ada sampah, dan algoritma yang mendasari adalah sebagai cepat karena mendapat.sumber
Ini pemahaman saya bahwa GetHashCode () asli mengembalikan alamat memori objek, jadi penting untuk menimpanya jika Anda ingin membandingkan dua objek yang berbeda.
Diedit: Itu tidak benar, metode GetHashCode () asli tidak dapat menjamin kesetaraan 2 nilai. Meskipun objek yang sama mengembalikan kode hash yang sama.
sumber
Menurut saya, refleksi di bawah ini menggunakan opsi yang lebih baik mengingat properti publik karena dengan ini Anda tidak perlu khawatir tentang penambahan / penghapusan properti (meskipun tidak begitu skenario umum). Ini saya temukan berkinerja lebih baik juga. (Dibandingkan waktu menggunakan Diagonistics stop watch).
sumber