Pertanyaan Mengapa penting untuk mengganti GetHashCode saat metode Setara ditimpa?


Diberikan 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;

        return fooItem.FooId == this.FooId;
    }

    public override int GetHashCode()
    {
        // Which is preferred?

        return base.GetHashCode();

        //return this.FooId.GetHashCode();
    }
}

Saya telah mengesampingkan Equals metode karena Foo mewakili deretan untuk Foomeja. Yang merupakan metode yang disukai untuk mengesampingkan GetHashCode?

Mengapa penting untuk diganti GetHashCode?


1164
2017-12-16 13:41


asal


Jawaban:


Ya, penting jika item Anda akan digunakan sebagai kunci dalam kamus, atau HashSet<T>, dll - karena ini digunakan (tanpa adanya suatu kebiasaan IEqualityComparer<T>) untuk mengelompokkan item ke dalam keranjang. Jika kode hash untuk dua item tidak cocok, mungkin tak pernah dianggap sama (Equals hanya tidak akan pernah dipanggil).

Itu GetHashCode() metode harus mencerminkan Equals logika; aturannya adalah:

  • jika dua hal itu sama (Equals(...) == true) kemudian mereka harus mengembalikan nilai yang sama untuk GetHashCode()
  • jika GetHashCode() sama, itu tidak diperlukan agar mereka menjadi sama; ini adalah tabrakan, dan Equals akan dipanggil untuk melihat apakah itu kesetaraan nyata atau tidak.

Dalam hal ini, sepertinya "return FooId;"Adalah yang cocok GetHashCode() pelaksanaan. Jika Anda menguji beberapa properti, biasanya menggabungkannya menggunakan kode seperti di bawah ini, untuk mengurangi tabrakan diagonal (yaitu agar new Foo(3,5) memiliki hash-code yang berbeda new Foo(5,3)):

int hash = 13;
hash = (hash * 7) + field1.GetHashCode();
hash = (hash * 7) + field2.GetHashCode();
...
return hash;

Oh - untuk kenyamanan, Anda mungkin juga mempertimbangkan untuk menyediakan == dan != operator saat menimpa Equals dan GetHashCode.


Sebuah demonstrasi tentang apa yang terjadi ketika Anda melakukan kesalahan ini sini.


1097
2017-12-16 13:47



Sebenarnya sangat sulit untuk diterapkan GetHashCode() benar karena, di samping aturan yang telah disebutkan Marc, kode hash tidak boleh berubah selama masa suatu objek. Oleh karena itu, bidang yang digunakan untuk menghitung kode hash harus tidak dapat diubah.

Saya akhirnya menemukan solusi untuk masalah ini ketika saya bekerja dengan NHibernate. Pendekatan saya adalah menghitung kode hash dari ID objek. ID hanya dapat diatur meskipun konstruktor jadi jika Anda ingin mengubah ID, yang sangat tidak mungkin, Anda harus membuat objek baru yang memiliki ID baru dan karena itu kode hash baru. Pendekatan ini bekerja paling baik dengan GUID karena Anda dapat memberikan konstruktor parameterless yang secara acak menghasilkan ID.


114
2017-12-21 12:39



Dengan mengesampingkan Setara Anda pada dasarnya menyatakan bahwa Anda adalah orang yang tahu lebih baik 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:

public override int GetHashCode()
{
    unchecked
    {
        var result = 0;
        result = (result * 397) ^ m_someVar1;
        result = (result * 397) ^ m_someVar2;
        result = (result * 397) ^ m_someVar3;
        result = (result * 397) ^ m_someVar4;
        return result;
    }
}

Seperti yang Anda lihat, ia hanya mencoba menebak kode hash yang bagus berdasarkan semua bidang di kelas, tetapi karena Anda tahu domain atau rentang nilai objek Anda, Anda masih dapat memberikan yang lebih baik.


41
2017-12-16 13:48



Harap jangan lupa untuk memeriksa parameter obj terhadap null ketika menimpa Equals(). Dan juga bandingkan jenisnya.

public override bool Equals(object obj)
{
    if (obj == null || GetType() != obj.GetType())
        return false;

    Foo fooItem = obj as Foo;

    return fooItem.FooId == this.FooId;
}

Alasannya adalah: Equals harus kembali salah jika dibandingkan dengan null. Lihat juga http://msdn.microsoft.com/en-us/library/bsc2ak47.aspx


32
2017-11-17 07:46



Bagaimana tentang:

public override int GetHashCode()
{
    return string.Format("{0}_{1}_{2}", prop1, prop2, prop3).GetHashCode();
}

Mengasumsikan kinerja bukan masalah :)


23
2017-11-25 00:48



Itu karena framework mengharuskan dua objek yang sama harus memiliki hashcode yang sama. Jika Anda mengganti metode yang sama untuk melakukan perbandingan khusus dua objek dan dua objek dianggap sama dengan metode, maka kode hash dari dua objek juga harus sama. (Kamus dan Hashtable bergantung pada prinsip ini).


9
2017-12-16 13:48



Hanya untuk menambahkan jawaban di atas:

Jika Anda tidak menimpa Sama maka perilaku default adalah referensi dari objek yang dibandingkan. Hal yang sama berlaku untuk hashcode - implmentasi default biasanya didasarkan pada alamat memori referensi. Karena Anda menimpa Sama artinya itu perilaku yang benar adalah untuk membandingkan apa pun yang Anda terapkan pada Sama 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 yang sama, misalnya metode linq yang menggunakan IEqualityComparer pertama membandingkan hashcodes dan hanya jika mereka sama, mereka akan membandingkan metode Equals () yang mungkin lebih mahal untuk menjalankan, jika kita tidak mengimplementasikan hashcode, objek yang sama mungkin akan memiliki hashcodes yang berbeda (karena mereka memiliki alamat memori yang berbeda) dan akan ditentukan secara salah sebagai tidak sama (Setara () bahkan tidak akan memukul).

Selain itu, kecuali masalah bahwa Anda mungkin tidak dapat menemukan objek Anda jika Anda menggunakannya dalam kamus (karena disisipkan oleh satu kode hash dan ketika Anda mencarinya kode hash default mungkin akan berbeda dan lagi sama dengan () Bahkan tidak akan disebut, seperti Marc Gravell menjelaskan dalam jawabannya, Anda juga memperkenalkan pelanggaran kamus atau konsep hashset yang seharusnya tidak mengizinkan kunci identik - Anda sudah menyatakan bahwa objek-objek itu pada dasarnya sama ketika Anda mengesampingkan Sama sehingga Anda tidak ingin keduanya sebagai kunci yang berbeda pada struktur data yang seharusnya memiliki kunci yang unik. Tetapi karena mereka memiliki hashcode yang berbeda, kunci yang "sama" akan dimasukkan sebagai yang berbeda.


8
2017-11-12 13:48



Kami memiliki dua masalah untuk diatasi.

  1. Anda tidak dapat memberikan sesuatu yang masuk akal GetHashCode() jika ada bidang di objek dapat diubah. Juga sering sebuah objek TIDAK AKAN PERNAH digunakan dalam koleksi yang tergantung pada GetHashCode(). Jadi biaya implementasi GetHashCode() sering tidak layak, atau tidak mungkin.

  2. Jika seseorang menempatkan objek Anda dalam koleksi yang memanggil GetHashCode() dan Anda telah menimpanya Equals() tanpa juga membuat GetHashCode() berperilaku dengan cara yang benar, orang itu dapat menghabiskan hari melacak masalah.

Oleh karena itu secara default saya lakukan.

public class Foo
{
    public int FooId { get; set; }
    public string FooName { get; set; }

    public override bool Equals(object obj)
    {
        Foo fooItem = obj as Foo;

        return fooItem.FooId == this.FooId;
    }

    public override int GetHashCode()
    {
        // Some comment to explain if there is a real problem with providing GetHashCode() 
        // or if I just don't see a need for it for the given class
        throw new Exception("Sorry I don't know what GetHashCode should do for this class");
    }
}

7
2017-11-19 10:17



Kode hash digunakan untuk koleksi berbasis hash seperti Kamus, Hashtable, HashSet, dll. Tujuan dari kode ini adalah untuk sangat cepat memilah objek tertentu dengan memasukkannya ke dalam grup tertentu (bucket). Pra-penyortiran 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 daripada di semua objek yang dikandungnya. Distribusi yang lebih baik dari kode hash (keunikan yang lebih baik) dengan pengambilan yang lebih cepat. Dalam situasi ideal di mana setiap objek memiliki kode hash yang unik, menemukannya adalah operasi O (1). Dalam banyak kasus mendekati O (1).


5
2018-02-21 11:36



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 mengetahui 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 menyingkirkan peringatan yang mengganggu oleh kompiler) saya hanya menggunakan:

   public override int GetHashCode()
   {
      return base.GetHashCode();
   }

(Tentu saja saya bisa menggunakan #pragma untuk mematikan peringatan juga, tetapi saya lebih memilih cara ini.)

Ketika Anda berada di posisi yang Anda melakukan perlu kinerja dari semua masalah yang disebutkan oleh orang lain di sini berlaku, tentu saja. Yang terpenting - jika tidak, Anda akan mendapatkan hasil yang salah saat mengambil item dari kumpulan atau kamus hash: kode hash tidak boleh berbeda dengan waktu hidup suatu objek (lebih akurat, selama waktu kapan kode hash diperlukan, seperti ketika menjadi kunci dalam kamus): misalnya, yang berikut ini salah karena Nilai bersifat publik dan sehingga dapat diubah secara eksternal ke kelas selama waktu hidup contoh, jadi Anda tidak harus menggunakannya sebagai dasar untuk kode hash:


   class A
   {
      public int Value;

      public override int GetHashCode()
      {
         return Value.GetHashCode(); //WRONG! Value is not constant during the instance's life time
      }
   }    

Di sisi lain, jika Nilai tidak dapat diubah, tidak apa-apa untuk digunakan:


   class A
   {
      public readonly int Value;

      public override int GetHashCode()
      {
         return Value.GetHashCode(); //OK  Value is read-only and can't be changed during the instance's life time
      }
   }


3
2018-06-26 23:21



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 menjamin kesetaraan 2 nilai. Padahal objek yang sama mengembalikan kode hash yang sama.


0
2017-10-07 17:06