Saya telah menjelaskan kebingungan ini dalam sebuah blog di https://www.spicelogic.com/Blog/net-event-handler-memory-leak-16 . Saya akan mencoba merangkumnya di sini sehingga Anda dapat memiliki gagasan yang jelas.
Referensi berarti, "Perlu":
Pertama-tama, Anda perlu memahami bahwa, jika objek A memiliki referensi ke objek B, maka, itu berarti, objek A membutuhkan objek B agar berfungsi, bukan? Jadi, pengumpul sampah tidak akan mengumpulkan objek B selama objek A masih hidup dalam memori.
Saya pikir bagian ini harus jelas bagi pengembang.
+ = Berarti, menyuntikkan referensi objek sisi kanan ke objek kiri:
Tetapi, kebingungan berasal dari operator C # + =. Operator ini tidak dengan jelas memberi tahu pengembang bahwa, sisi kanan operator ini sebenarnya menyuntikkan referensi ke objek sisi kiri.
Dan dengan melakukan itu, objek A berpikir, perlu objek B, meskipun, dari sudut pandang Anda, objek A tidak peduli jika objek B hidup atau tidak. Karena objek A menganggap objek B diperlukan, objek A melindungi objek B dari pengumpul sampah selama objek A masih hidup. Tetapi, jika Anda tidak ingin perlindungan diberikan kepada objek pelanggan acara, maka, Anda dapat mengatakan, kebocoran memori terjadi.
Anda dapat menghindari kebocoran seperti itu dengan melepaskan event handler.
Bagaimana cara mengambil keputusan?
Tapi, ada banyak acara dan penangan acara di seluruh basis kode Anda. Apakah itu berarti, Anda harus terus melepaskan penangan acara di mana-mana? Jawabannya adalah Tidak. Jika Anda harus melakukannya, basis kode Anda akan sangat jelek dengan verbose.
Anda bisa mengikuti bagan alur sederhana untuk menentukan apakah penangan kejadian yang memisahkan diperlukan atau tidak.
Sebagian besar waktu, Anda mungkin menemukan objek acara pelanggan sama pentingnya dengan objek penerbit acara dan keduanya seharusnya hidup pada waktu yang sama.
Contoh skenario di mana Anda tidak perlu khawatir
Misalnya, acara klik tombol dari sebuah jendela.
Di sini, penerbit acara adalah Tombol, dan pelanggan acara adalah MainWindow. Menerapkan bagan alur itu, ajukan pertanyaan, apakah Jendela Utama (pelanggan acara) seharusnya sudah mati sebelum Tombol (penerbit acara)? Jelas Tidak. Benar? Itu bahkan tidak masuk akal. Lalu, mengapa khawatir tentang melepaskan event handler klik?
Contoh ketika pelepasan event handler adalah HARUS.
Saya akan memberikan satu contoh di mana objek pelanggan seharusnya sudah mati sebelum objek penerbit. Katakan, MainWindow Anda menerbitkan acara yang bernama "SomethingHappened" dan Anda menampilkan jendela anak dari jendela utama dengan mengklik tombol. Jendela anak berlangganan acara jendela utama itu.
Dan, jendela anak berlangganan acara Jendela Utama.
Dari kode ini, kita dapat dengan jelas memahami bahwa ada tombol di Jendela Utama. Mengklik tombol itu menunjukkan Window Anak. Jendela anak mendengarkan acara dari jendela utama. Setelah melakukan sesuatu, pengguna menutup jendela anak.
Sekarang, sesuai dengan bagan alur yang saya berikan jika Anda mengajukan pertanyaan "Apakah jendela anak (pelanggan acara) seharusnya sudah mati sebelum penerbit acara (jendela utama)? Jawabannya harus YA. Benar? Jadi, lepaskan penyelenggara acara Saya biasanya melakukan itu dari event Window yang tidak diturunkan.
Aturan praktis: Jika tampilan Anda (yaitu WPF, WinForm, UWP, Formulir Xamarin, dll.) Berlangganan ke acara ViewModel, selalu ingat untuk melepaskan pengendali acara. Karena ViewModel biasanya hidup lebih lama daripada tampilan. Jadi, jika ViewModel tidak dihancurkan, setiap tampilan yang berlangganan acara dari ViewModel itu akan tetap tersimpan dalam memori, yang tidak baik.
Bukti konsep menggunakan memory profiler.
Tidak akan terlalu menyenangkan jika kita tidak dapat memvalidasi konsep dengan profiler memori. Saya telah menggunakan JetBrain dotMemory profiler dalam percobaan ini.
Pertama, saya telah menjalankan MainWindow, yang muncul seperti ini:
Kemudian, saya mengambil snapshot memori. Lalu saya mengklik tombol 3 kali . Tiga jendela anak muncul. Saya telah menutup semua jendela anak dan mengklik tombol Force GC di profiler dotMemory untuk memastikan bahwa Pengumpul Sampah dipanggil. Kemudian, saya mengambil snapshot memori lain dan membandingkannya. Melihat! Ketakutan kami benar. Jendela Anak tidak dikumpulkan oleh pengumpul Sampah bahkan setelah mereka ditutup. Tidak hanya itu, tetapi jumlah objek bocor untuk objek ChildWindow juga ditampilkan " 3 " (Saya mengklik tombol 3 kali untuk menampilkan 3 jendela anak).
Ok, kalau begitu, saya melepaskan event handler seperti yang ditunjukkan di bawah ini.
Kemudian, saya telah melakukan langkah yang sama dan memeriksa memori profiler. Kali ini, wow! tidak ada lagi kebocoran memori.