.NET IDisposable Pattern menyiratkan bahwa jika Anda menulis finalizer, dan mengimplementasikan IDisposable, finalizer Anda perlu secara eksplisit memanggil Dispose. Ini logis, dan itulah yang selalu saya lakukan dalam situasi langka di mana seorang finalis dijamin.
Namun, apa yang terjadi jika saya hanya melakukan ini:
class Foo : IDisposable
{
public void Dispose(){ CloseSomeHandle(); }
}
dan tidak menerapkan finalizer, atau apa pun. Akankah kerangka kerja memanggil metode Buang untuk saya?
Ya, saya menyadari ini terdengar bodoh, dan semua logika menyiratkan bahwa itu tidak akan terjadi, tetapi saya selalu memiliki 2 hal di belakang kepala saya yang membuat saya tidak yakin.
Seseorang beberapa tahun yang lalu pernah mengatakan kepada saya bahwa itu sebenarnya akan melakukan ini, dan orang itu memiliki rekam jejak yang sangat kuat dalam "mengetahui barang-barang mereka."
Kompilator / kerangka kerja melakukan hal-hal 'ajaib' lain tergantung pada antarmuka apa yang Anda terapkan (misalnya: foreach, metode ekstensi, serialisasi berdasarkan atribut, dll), jadi masuk akal bahwa ini mungkin juga 'ajaib'.
Sementara saya sudah membaca banyak hal tentang hal itu, dan sudah ada banyak hal tersirat, aku tidak pernah bisa menemukan definitif Ya atau Tidak ada jawaban untuk pertanyaan ini.
sumber
Saya ingin menekankan poin Brian dalam komentarnya, karena itu penting.
Finalizer bukanlah destruktor deterministik seperti di C ++. Seperti orang lain telah menunjukkan, tidak ada jaminan ketika itu akan disebut, dan memang jika Anda memiliki cukup memori, jika akan pernah disebut.
Tetapi hal buruk tentang finalisator adalah, seperti yang dikatakan Brian, hal itu menyebabkan objek Anda tetap berada dalam pengumpulan sampah. Ini bisa jadi buruk. Mengapa?
Seperti yang Anda mungkin atau mungkin tidak tahu, GC dibagi menjadi beberapa generasi - Gen 0, 1 dan 2, ditambah Large Object Heap. Split adalah istilah yang longgar - Anda mendapatkan satu blok memori, tetapi ada petunjuk di mana objek Gen 0 dimulai dan diakhiri.
Proses berpikirnya adalah Anda kemungkinan besar akan menggunakan banyak objek yang berumur pendek. Jadi itu harus mudah dan cepat bagi GC untuk mendapatkan - objek Gen 0. Jadi ketika ada tekanan memori, hal pertama yang dilakukannya adalah koleksi Gen 0.
Sekarang, jika itu tidak menyelesaikan cukup tekanan, maka itu kembali dan melakukan sapuan Gen 1 (mengulangi Gen 0), dan kemudian jika masih belum cukup, ia melakukan sapuan Gen 2 (mengulangi Gen 1 dan Gen 0). Jadi membersihkan benda yang berumur panjang bisa memakan waktu cukup lama dan biayanya agak mahal (karena utas Anda mungkin tersangkut selama pengoperasian).
Artinya jika Anda melakukan sesuatu seperti ini:
~MyClass() { }
Objek Anda, apa pun yang terjadi, akan ditampilkan ke Generasi 2. Ini karena GC tidak memiliki cara untuk memanggil finalizer selama pengumpulan sampah. Jadi objek yang harus diselesaikan dipindahkan ke antrian khusus untuk dibersihkan oleh utas yang berbeda (utas finalizer - yang jika Anda membunuh membuat semua jenis hal buruk terjadi). Ini berarti objek Anda bertahan lebih lama, dan berpotensi memaksa lebih banyak pengumpulan sampah.
Jadi, semua itu hanya untuk menyampaikan poin bahwa Anda ingin menggunakan IDisposable untuk membersihkan sumber daya bila memungkinkan dan secara serius mencoba mencari cara untuk menggunakan finalizer. Ini demi kepentingan terbaik aplikasi Anda.
sumber
Sudah ada banyak diskusi bagus di sini, dan saya sedikit terlambat ke pesta, tetapi saya ingin menambahkan beberapa poin sendiri.
class SomeObject : IDisposable { IntPtr _SomeNativeHandle; FileStream _SomeFileStream; // Something useful here ~ SomeObject() { Dispose(false); } public void Dispose() { Dispose(true); } protected virtual void Dispose(bool disposing) { if(disposing) { GC.SuppressFinalize(this); //Because the object was explicitly disposed, there will be no need to //run the finalizer. Suppressing it reduces pressure on the GC //The managed reference to an IDisposable is disposed only if the _SomeFileStream.Dispose(); } //Regardless, clean up the native handle ourselves. Because it is simple a member // of the current instance, the GC can't have done anything to it, // and this is the onlyplace to safely clean up if(IntPtr.Zero != _SomeNativeHandle) { NativeMethods.CloseHandle(_SomeNativeHandle); _SomeNativeHandle = IntPtr.Zero; } } }
Itu adalah versi sederhana, tetapi ada banyak nuansa yang dapat membuat Anda tersandung pada pola ini.
Menurut pendapat saya, jauh lebih baik untuk sepenuhnya menghindari jenis apa pun yang secara langsung berisi referensi sekali pakai dan sumber daya asli yang mungkin memerlukan penyelesaian. SafeHandles menyediakan cara yang sangat bersih untuk melakukan ini dengan mengenkapsulasi sumber daya asli menjadi sekali pakai yang secara internal menyediakan penyelesaiannya sendiri (bersama dengan sejumlah manfaat lain seperti menghapus jendela selama P / Invoke di mana pegangan asli bisa hilang karena pengecualian asinkron) .
Hanya dengan mendefinisikan SafeHandle membuat ini Sepele:
private class SomeSafeHandle : SafeHandleZeroOrMinusOneIsInvalid { public SomeSafeHandle() : base(true) { } protected override bool ReleaseHandle() { return NativeMethods.CloseHandle(handle); } }
Memungkinkan Anda untuk menyederhanakan tipe yang memuat menjadi:
class SomeObject : IDisposable { SomeSafeHandle _SomeSafeHandle; FileStream _SomeFileStream; // Something useful here public virtual void Dispose() { _SomeSafeHandle.Dispose(); _SomeFileStream.Dispose(); } }
sumber
GC.SuppressFinalize
dalam contoh ini. Dalam konteks ini, SuppressFinalize hanya boleh dipanggil jikaDispose(true)
berhasil dijalankan. JikaDispose(true)
gagal di beberapa titik setelah finalisasi disembunyikan tetapi sebelum semua sumber daya (terutama yang tidak dikelola) dibersihkan, Anda masih ingin penyelesaian terjadi untuk melakukan pembersihan sebanyak mungkin. Lebih baik memindahkanGC.SuppressFinalize
panggilan keDispose()
metode setelah panggilan keDispose(true)
. Lihat Pedoman Desain Kerangka dan posting ini .Saya rasa tidak. Anda memiliki kendali atas kapan Buang dipanggil, yang berarti Anda secara teori dapat menulis kode pembuangan yang membuat asumsi tentang (misalnya) keberadaan objek lain. Anda tidak memiliki kendali atas kapan finalizer dipanggil, jadi akan sulit jika finalizer secara otomatis memanggil Buang atas nama Anda.
EDIT: Saya pergi dan menguji, hanya untuk memastikan:
class Program { static void Main(string[] args) { Fred f = new Fred(); f = null; GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine("Fred's gone, and he's not coming back..."); Console.ReadLine(); } } class Fred : IDisposable { ~Fred() { Console.WriteLine("Being finalized"); } void IDisposable.Dispose() { Console.WriteLine("Being Disposed"); } }
sumber
Tidak dalam kasus yang Anda gambarkan, Tapi GC akan memanggil Finalizer untuk Anda, jika Anda memilikinya.
NAMUN. Pengumpulan sampah berikutnya, alih-alih dikumpulkan, objek akan masuk ke antrian finalisasi, semuanya dikumpulkan, lalu finalisasinya dipanggil. Koleksi selanjutnya setelah itu akan dibebaskan.
Bergantung pada tekanan memori aplikasi Anda, Anda mungkin tidak memiliki gc untuk pembuatan objek tersebut untuk sementara waktu. Jadi dalam kasus, katakanlah, aliran file atau koneksi db, Anda mungkin harus menunggu beberapa saat agar sumber daya yang tidak dikelola dibebaskan dalam panggilan finalizer untuk sementara waktu, menyebabkan beberapa masalah.
sumber
Tidak, itu tidak disebut.
Tapi ini memudahkan untuk tidak lupa membuang benda Anda. Cukup gunakan
using
kata kunci.Saya melakukan tes berikut untuk ini:
class Program { static void Main(string[] args) { Foo foo = new Foo(); foo = null; Console.WriteLine("foo is null"); GC.Collect(); Console.WriteLine("GC Called"); Console.ReadLine(); } } class Foo : IDisposable { public void Dispose() { Console.WriteLine("Disposed!"); }
sumber
GC tidak akan memanggil buang. Ini mungkin memanggil finalizer Anda, tetapi bahkan ini tidak dijamin dalam semua keadaan.
Lihat artikel ini untuk diskusi tentang cara terbaik menangani ini.
sumber
Dokumentasi IDisposable memberikan penjelasan yang cukup jelas dan rinci tentang perilaku, serta contoh kode. GC TIDAK akan memanggil
Dispose()
metode pada antarmuka, tetapi akan memanggil finalizer untuk objek Anda.sumber
Pola IDisposable dibuat terutama untuk dipanggil oleh pengembang, jika Anda memiliki objek yang mengimplementasikan IDispose pengembang harus mengimplementasikan
using
kata kunci di sekitar konteks objek atau memanggil metode Dispose secara langsung.Keamanan gagal untuk pola tersebut adalah dengan mengimplementasikan finalizer yang memanggil metode Dispose (). Jika Anda tidak melakukannya, Anda dapat membuat beberapa kebocoran memori yaitu: Jika Anda membuat beberapa pembungkus COM dan tidak pernah memanggil System.Runtime.Interop.Marshall.ReleaseComObject (comObject) (yang akan ditempatkan di metode Buang).
Tidak ada keajaiban di clr untuk memanggil metode Buang secara otomatis selain melacak objek yang berisi finalizer dan menyimpannya di tabel Finalizer oleh GC dan memanggilnya ketika beberapa heuristik pembersihan dilakukan oleh GC.
sumber