Penting untuk memisahkan pembuangan dari pengumpulan sampah. Mereka benar-benar hal yang terpisah, dengan satu kesamaan yang akan saya bahas sebentar lagi.
Dispose
, pengumpulan dan penyelesaian sampah
Saat Anda menulis using
pernyataan, itu hanyalah gula sintaksis untuk blok coba / akhirnya sehingga yang Dispose
dipanggil bahkan jika kode di badan using
pernyataan itu mengeluarkan pengecualian. Ini tidak berarti bahwa objek tersebut adalah sampah yang dikumpulkan di akhir blok.
Pembuangan adalah tentang sumber daya yang tidak dikelola ( sumber daya non-memori). Ini bisa berupa pegangan UI, koneksi jaringan, pegangan file, dll. Ini adalah sumber daya yang terbatas, jadi Anda biasanya ingin merilisnya secepat mungkin. Anda harus mengimplementasikan IDisposable
setiap kali tipe Anda "memiliki" sumber daya yang tidak dikelola, baik secara langsung (biasanya melalui IntPtr
) atau tidak langsung (misalnya melalui a Stream
, a SqlConnection
dll).
Pengumpulan sampah itu sendiri hanya tentang memori - dengan satu putaran kecil. Pengumpul sampah dapat menemukan objek yang tidak lagi dapat dirujuk, dan membebaskannya. Ia tidak mencari sampah sepanjang waktu - hanya ketika mendeteksi bahwa ia perlu (misalnya jika satu "generasi" dari heap kehabisan memori).
Perubahan itu adalah finalisasi . Pengumpul sampah menyimpan daftar objek yang tidak lagi dapat dijangkau, tetapi memiliki finalizer (ditulis seperti ~Foo()
dalam C #, agak membingungkan - tidak seperti C ++ destructors). Ini menjalankan finalizer pada objek-objek ini, kalau-kalau mereka perlu melakukan pembersihan ekstra sebelum memori mereka dibebaskan.
Finalizer hampir selalu digunakan untuk membersihkan sumber daya jika pengguna jenis tersebut lupa membuangnya secara tertib. Jadi jika Anda membuka FileStream
tetapi lupa untuk memanggil Dispose
atau Close
, finalizer pada akhirnya akan melepaskan pegangan file yang mendasarinya untuk Anda. Dalam program yang ditulis dengan baik, finalisator seharusnya tidak pernah menyala menurut saya.
Menetapkan variabel ke null
Satu hal kecil tentang menyetel variabel ke null
- ini hampir tidak pernah diperlukan demi pengumpulan sampah. Terkadang Anda mungkin ingin melakukannya jika itu adalah variabel anggota, meskipun menurut pengalaman saya jarang "bagian" dari suatu objek tidak lagi diperlukan. Ketika itu adalah variabel lokal, JIT biasanya cukup pintar (dalam mode rilis) untuk mengetahui kapan Anda tidak akan menggunakan referensi lagi. Sebagai contoh:
StringBuilder sb = new StringBuilder();
sb.Append("Foo");
string x = sb.ToString();
// The string and StringBuilder are already eligible
// for garbage collection here!
int y = 10;
DoSomething(y);
// These aren't helping at all!
x = null;
sb = null;
// Assume that x and sb aren't used here
Satu waktu di mana mungkin layak untuk mengatur variabel lokal null
adalah ketika Anda berada dalam loop, dan beberapa cabang dari loop perlu menggunakan variabel tetapi Anda tahu Anda telah mencapai titik di mana Anda tidak melakukannya. Sebagai contoh:
SomeObject foo = new SomeObject();
for (int i=0; i < 100000; i++)
{
if (i == 5)
{
foo.DoSomething();
// We're not going to need it again, but the JIT
// wouldn't spot that
foo = null;
}
else
{
// Some other code
}
}
Menerapkan IDisposable / finalizers
Jadi, haruskah tipe Anda menerapkan finalizer? Hampir pasti tidak. Jika Anda hanya memiliki sumber daya yang tidak dikelola secara tidak langsung (misalnya Anda memiliki FileStream
variabel sebagai anggota), maka menambahkan finalizer Anda sendiri tidak akan membantu: aliran hampir pasti memenuhi syarat untuk pengumpulan sampah saat objek Anda, jadi Anda dapat mengandalkan FileStream
memiliki finalizer (jika perlu - ini mungkin merujuk ke sesuatu yang lain, dll). Jika Anda ingin memiliki sumber daya yang tidak dikelola "hampir" secara langsung, SafeHandle
itu teman Anda - dibutuhkan sedikit waktu untuk memulai, tetapi itu berarti Anda hampir tidak perlu menulis finalizer lagi . Anda biasanya hanya memerlukan finalizer jika Anda memiliki pegangan yang benar-benar langsung pada sumber daya (an IntPtr
) dan Anda harus melihat untuk pindah keSafeHandle
secepat yang kamu bisa. (Ada dua tautan di sana - baca keduanya, idealnya.)
Joe Duffy memiliki seperangkat pedoman yang sangat panjang seputar finalisator dan IDisposable (ditulis bersama dengan banyak orang pintar) yang layak dibaca. Perlu diketahui bahwa jika Anda menyegel kelas Anda, itu membuat hidup jauh lebih mudah: pola penimpaan Dispose
untuk memanggil Dispose(bool)
metode virtual baru dll hanya relevan ketika kelas Anda dirancang untuk warisan.
Ini memang sedikit bertele-tele, tapi tolong tanyakan klarifikasi di mana Anda ingin :)