Apa perbedaan antara menggunakan IDisposable vs destruktor di C #?

101

Kapan saya mengimplementasikan IDispose di kelas sebagai lawan dari destruktor? Saya membaca artikel ini , tetapi saya masih kehilangan intinya.

Asumsi saya adalah bahwa jika saya mengimplementasikan IDispose pada suatu objek, saya dapat secara eksplisit 'merusak' sebagai lawan menunggu pengumpul sampah untuk melakukannya. Apakah ini benar?

Apakah itu berarti saya harus selalu secara eksplisit memanggil Buang pada suatu objek? Apa saja contoh umum dari ini?

Jordan Parmer
sumber
5
Memang, Anda harus memanggil Buang pada setiap benda sekali pakai. Anda dapat melakukannya dengan mudah menggunakan usingkonstruksi.
Luc Touraille
Ah, masuk akal. Saya selalu bertanya-tanya mengapa pernyataan 'menggunakan' digunakan untuk aliran file. Saya tahu itu ada hubungannya dengan ruang lingkup objek, tetapi saya tidak meletakkannya dalam konteks dengan antarmuka IDisposable.
Jordan Parmer
5
Satu hal penting untuk diingat adalah bahwa finalis tidak boleh mengakses anggota kelas yang dikelola, karena anggota tersebut mungkin bukan lagi referensi yang valid.
Dan Bryant

Jawaban:

126

Finalizer (alias destruktor) adalah bagian dari pengumpulan sampah (GC) - tidak dapat ditentukan ketika (atau bahkan jika) ini terjadi, karena GC terutama terjadi sebagai akibat dari tekanan memori (yaitu membutuhkan lebih banyak ruang). Finalizer biasanya hanya digunakan untuk membersihkan sumber daya yang tidak dikelola , karena sumber daya yang dikelola akan memiliki pengumpulan / pembuangannya sendiri.

Karenanya IDisposabledigunakan untuk membersihkan objek secara deterministik , yaitu sekarang. Itu tidak mengumpulkan memori objek (yang masih milik GC) - tetapi digunakan misalnya untuk menutup file, koneksi database, dll.

Ada banyak topik sebelumnya tentang ini:

Terakhir, perhatikan bahwa tidak jarang suatu IDisposableobjek juga memiliki finalizer; dalam hal ini, Dispose()biasanya panggilan GC.SuppressFinalize(this), yang berarti bahwa GC tidak menjalankan finalizer - itu hanya membuang memori (jauh lebih murah). Finalizer tetap berjalan jika Anda lupa Dispose()objeknya.

Marc Gravell
sumber
Terima kasih! Itu sangat masuk akal. Saya sangat menghargai tanggapan yang luar biasa.
Jordan Parmer
27
Satu hal ekstra untuk dikatakan. Jangan menambahkan finalizer ke kelas Anda kecuali Anda benar-benar membutuhkannya. Jika Anda menambahkan finalizer (destruktor), GC harus memanggilnya (bahkan finalizer kosong) dan memanggilnya objek akan selalu bertahan dari pengumpulan sampah gen 1. Ini akan menghambat dan memperlambat GC. Itulah yang dikatakan Marc untuk memanggil SuppressFinalize dalam kode di atas
Kevin Jones
1
Jadi Finalize adalah merilis resource yang tidak terkelola. Tapi Buang dapat digunakan untuk melepaskan sumber daya yang dikelola dan tidak dikelola?
Dark_Knight
2
@ Ya; karena 6 level di bawah rantai pengelolaan bisa menjadi salah satu yang tidak terkelola yang membutuhkan pembersihan segera
Marc Gravell
1
@KevinJ Objects dengan finalizer dijamin bertahan dari gen 0, bukan 1, bukan? Saya membacanya di sebuah buku berjudul .NET Performance.
David Klempfner
25

Peran Finalize()metode ini adalah untuk memastikan bahwa objek .NET dapat membersihkan sumber daya yang tidak dikelola saat sampah dikumpulkan . Namun, objek seperti koneksi database atau penangan file harus dirilis sesegera mungkin, alih-alih mengandalkan pengumpulan sampah. Untuk itu Anda harus mengimplementasikan IDisposableantarmuka, dan melepaskan sumber daya Anda dalam Dispose()metode tersebut.

Igal Tabachnik
sumber
9

Ada deskripsi yang sangat bagus tentang MSDN :

Kegunaan utama dari antarmuka ini adalah untuk melepaskan sumber daya yang tidak dikelola . Pengumpul sampah secara otomatis melepaskan memori yang dialokasikan ke objek yang dikelola saat objek tersebut tidak lagi digunakan. Namun, tidak mungkin untuk memprediksi kapan pengumpulan sampah akan terjadi . Selain itu, pengumpul sampah tidak memiliki pengetahuan tentang sumber daya yang tidak dikelola seperti gagang jendela, atau membuka file dan aliran.

Gunakan metode Buang antarmuka ini untuk secara eksplisit melepaskan sumber daya yang tidak dikelola bersama dengan pengumpul sampah. The konsumen dari sebuah objek dapat memanggil metode ini ketika objek tidak lagi diperlukan.

abatishchev
sumber
1
Salah satu kelemahan utama dari deskripsi tersebut adalah bahwa MS memberikan contoh sumber daya yang tidak terkelola, tetapi dari apa yang saya lihat tidak pernah benar-benar mendefinisikan istilah tersebut. Karena objek yang dikelola umumnya hanya dapat digunakan dalam kode yang dikelola, orang mungkin berpikir hal-hal yang digunakan dalam kode yang tidak dikelola adalah sumber daya yang tidak dikelola, tetapi itu tidak benar. Banyak kode yang tidak terkelola tidak menggunakan sumber daya apa pun, dan beberapa jenis sumber daya yang tidak terkelola seperti peristiwa hanya ada di semesta kode terkelola.
supercat
1
Jika objek berumur pendek berlangganan ke acara dari objek berumur panjang (misalnya meminta untuk diberitahu tentang setiap perubahan yang terjadi dalam umur objek berumur pendek), kejadian seperti itu harus dianggap sebagai sumber daya yang tidak terkelola, karena kegagalan untuk unsubscribe acara tersebut akan menyebabkan umur dari objek berumur pendek diperpanjang untuk itu dari objek berumur panjang. Jika ribuan atau jutaan objek berumur pendek berlangganan ke suatu acara tetapi ditinggalkan tanpa berhenti berlangganan, itu dapat menyebabkan kebocoran memori atau CPU (karena waktu yang diperlukan untuk memproses setiap langganan akan meningkat).
supercat
1
Skenario lain yang melibatkan sumber daya yang tidak terkelola dalam kode yang dikelola adalah alokasi objek dari kumpulan. Terutama jika kode perlu dijalankan dalam .NET Micro Framework (yang pengumpul sampahnya jauh kurang efisien daripada yang ada di mesin desktop) mungkin berguna untuk kode yang memiliki, misalnya larik struktur, yang masing-masing dapat ditandai "digunakan" atau "gratis". Permintaan alokasi harus menemukan struktur yang saat ini ditandai "bebas", menandainya "digunakan", dan mengembalikan indeks padanya; permintaan rilis harus menandai struktur sebagai "bebas". Jika permintaan alokasi mengembalikan misalnya 23, maka ...
supercat
1
... jika kode tidak pernah memberi tahu pemilik array bahwa ia tidak lagi membutuhkan item # 23, slot array itu tidak akan pernah dapat digunakan oleh kode lain. Alokasi manual keluar dari slot array tidak terlalu sering digunakan dalam kode desktop karena GC cukup efisien, tetapi dalam kode yang berjalan pada Micro Framework dapat membuat perbedaan besar.
supercat
8

Satu-satunya hal yang harus ada di destruktor C # adalah baris ini:

Dispose(False);

Itu dia. Tidak ada hal lain yang harus dilakukan dalam metode itu.

Jonathan Allen
sumber
3
Ini adalah pola desain yang diusulkan oleh Microsoft dalam dokumentasi .NET, tetapi jangan gunakan ketika objek Anda tidak dapat dibuang. msdn.microsoft.com/en-us/library/fs2xkftw%28v=vs.110%29.aspx
Zbyl
1
Saya tidak dapat memikirkan alasan apa pun untuk menawarkan kelas dengan finalisator yang tidak juga memiliki metode Buang.
Jonathan Allen
4

Pertanyaan Anda tentang apakah Anda harus selalu menelepon atau tidak Disposebiasanya merupakan perdebatan sengit. Lihat blog ini untuk perspektif yang menarik dari individu yang dihormati di komunitas .NET.

Secara pribadi, saya pikir posisi Jeffrey Richter bahwa panggilan Disposetidak wajib sangatlah lemah. Dia memberikan dua contoh untuk membenarkan pendapatnya.

Dalam contoh pertama, dia mengatakan memanggil Disposekontrol Windows Forms membosankan dan tidak perlu dalam skenario umum. Namun, ia gagal menyebutkan bahwa Disposesebenarnya dipanggil secara otomatis oleh wadah kontrol dalam skenario arus utama tersebut.

Dalam contoh kedua dia menyatakan bahwa pengembang mungkin salah berasumsi bahwa instance dari IAsyncResult.WaitHandleharus dibuang secara agresif tanpa menyadari bahwa properti dengan malas menginisialisasi pegangan tunggu yang mengakibatkan penalti kinerja yang tidak perlu. Tapi, masalah dengan contoh ini adalah bahwa IAsyncResultitu sendiri tidak mematuhi pedoman yang diterbitkan Microsoft sendiri untuk menangani IDisposableobjek. Yaitu jika sebuah kelas memiliki referensi ke suatu IDisposabletipe maka kelas itu sendiri harus diimplementasikan IDisposable. Jika IAsyncResultmengikuti aturan itu maka Disposemetodenya sendiri bisa membuat keputusan mengenai anggota konstituen mana yang perlu dibuang.

Jadi, kecuali seseorang memiliki argumen yang lebih meyakinkan, saya akan tetap berada di kamp "selalu hubungi Buang" dengan pemahaman bahwa akan ada beberapa kasus pinggiran yang sebagian besar muncul karena pilihan desain yang buruk.

Brian Gideon
sumber
3

Ini sangat sederhana. Saya tahu ini telah dijawab tetapi saya akan mencoba lagi tetapi akan mencoba membuatnya sesederhana mungkin.

Penghancur biasanya tidak pernah digunakan. Ini hanya dijalankan .net ingin dijalankan. Ini hanya akan berjalan setelah siklus collectoin sampah. Ini mungkin tidak pernah benar-benar dijalankan selama siklus hidup aplikasi Anda. Untuk alasan ini, Anda tidak boleh memasukkan kode apa pun ke dalam destruktor yang 'harus' dijalankan. Anda juga tidak dapat mengandalkan objek apa pun yang ada di dalam kelas agar ada saat dijalankan (objek tersebut mungkin sudah dibersihkan karena urutan pengrusakan yang dijalankan tidak dijamin).

IDisposible harus digunakan setiap kali Anda memiliki objek yang membuat sumber daya yang perlu dibersihkan (misalnya, pegangan file dan grafik). Faktanya, banyak yang berpendapat bahwa apa pun yang Anda masukkan ke dalam destruktor harus dimasukkan ke IDdisposable karena alasan yang tercantum di atas.

Sebagian besar kelas akan memanggil dispose ketika finalizer dijalankan tetapi ini hanya ada sebagai penjaga yang aman dan tidak boleh diandalkan. Anda harus secara eksplisit membuang apa pun yang mengimplementasikan IDisposable setelah Anda selesai melakukannya. Jika Anda menerapkan IDisposable, Anda harus memanggil dispose di finalizer. Lihat http://msdn.microsoft.com/en-us/library/system.idisposable.aspx untuk contoh.

DaEagle
sumber
Tidak, pengumpul sampah tidak pernah memanggil Dispose (). Itu hanya memanggil finalizer.
Marc Gravell
Perbaiki itu. Kelas seharusnya memanggil buang di finalizer mereka, tetapi mereka tidak harus melakukannya.
DaEagle