Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Nota
Konten ini dicetak ulang oleh izin Pearson Education, Inc. dari Panduan Desain Kerangka Kerja: Konvensi, Idiom, dan Pola untuk Pustaka .NET yang Dapat Digunakan Kembali, Edisi ke-2. Edisi itu diterbitkan pada tahun 2008, dan buku tersebut telah sepenuhnya direvisi pada edisi ketiga. Beberapa informasi di halaman ini mungkin sudah kedaluarsa.
Semua program memperoleh satu atau beberapa sumber daya sistem, seperti memori, handel sistem, atau koneksi database, selama eksekusinya. Pengembang harus berhati-hati saat menggunakan sumber daya sistem tersebut, karena harus dirilis setelah diperoleh dan digunakan.
CLR menyediakan dukungan untuk manajemen memori otomatis. Memori terkelola (memori yang dialokasikan menggunakan operator newC# ) tidak perlu dirilis secara eksplisit. Dilepaskan secara otomatis oleh pengumpul sampah (GC). Ini membebaskan pengembang dari tugas yang melelahkan dan sulit untuk melepaskan memori dan telah menjadi salah satu alasan utama untuk produktivitas yang belum pernah terjadi sebelumnya yang diberikan oleh .NET Framework.
Sayangnya, memori terkelola hanyalah salah satu dari banyak jenis sumber daya sistem. Sumber daya selain memori terkelola masih perlu dirilis secara eksplisit dan disebut sebagai sumber daya yang tidak dikelola. GC secara khusus tidak dirancang untuk mengelola sumber daya yang tidak dikelola seperti itu, yang berarti bahwa tanggung jawab untuk mengelola sumber daya yang tidak dikelola terletak di tangan pengembang.
CLR menyediakan bantuan dalam melepaskan sumber daya yang tidak dikelola. System.Object menyatakan metode virtual Finalize (juga disebut penghancur) yang dipanggil oleh GC sebelum memori objek dikumpulkan kembali oleh GC dan dapat di-override untuk melepaskan sumber daya yang tidak dikelola. Jenis yang mengoverride finalizer disebut sebagai jenis yang dapat difinalisasi.
Meskipun finalizer efektif dalam beberapa skenario pembersihan, mereka memiliki dua kelemahan yang signifikan:
Finalizer dipanggil ketika GC mendeteksi bahwa objek memenuhi syarat untuk koleksi. Ini terjadi pada beberapa periode waktu yang tidak ditentukan setelah sumber daya tidak diperlukan lagi. Penundaan antara kapan pengembang dapat atau ingin merilis sumber daya dan waktu ketika sumber daya benar-benar dirilis oleh finalizer mungkin tidak dapat diterima dalam program yang memperoleh banyak sumber daya yang langka (sumber daya yang dapat dengan mudah habis) atau dalam kasus di mana sumber daya mahal untuk tetap digunakan (misalnya, buffer memori besar yang tidak dikelola).
Ketika CLR perlu memanggil finalizer, CLR harus menunda pengumpulan memori objek hingga siklus pengumpulan sampah otomatis berikutnya (finalizer berjalan di antara pengumpulan). Ini berarti bahwa memori objek (dan semua objek yang dirujuknya) tidak akan dirilis untuk jangka waktu yang lebih lama.
Oleh karena itu, hanya mengandalkan finalizer mungkin tidak sesuai dalam banyak skenario ketika penting untuk mendapatkan kembali sumber daya yang tidak dikelola secepat mungkin, ketika berhadapan dengan sumber daya yang terbatas, atau dalam skenario yang sangat menuntut kinerja tinggi di mana biaya tambahan dari GC untuk finalisasi tidak dapat diterima.
Framework menyediakan System.IDisposable antarmuka yang harus diimplementasikan untuk memberi pengembang cara manual untuk merilis sumber daya yang tidak dikelola segera setelah tidak diperlukan. Ini juga menyediakan GC.SuppressFinalize metode yang dapat memberi tahu GC bahwa objek dibuang secara manual dan tidak perlu diselesaikan lagi, dalam hal ini memori objek dapat diklaim kembali sebelumnya. Jenis yang mengimplementasikan IDisposable antarmuka disebut sebagai jenis sekali pakai.
Pola Pembuangan dimaksudkan untuk menstandarkan penggunaan dan implementasi finalizer dan antarmuka IDisposable.
Motivasi utama untuk pola ini adalah untuk mengurangi kompleksitas implementasi Finalize dan Dispose metode. Kompleksitas berasal dari fakta bahwa metode berbagi beberapa tetapi tidak semua jalur kode (perbedaannya dijelaskan kemudian dalam bab). Selain itu, ada alasan historis untuk beberapa elemen pola yang terkait dengan evolusi dukungan bahasa untuk manajemen sumber daya deterministik.
✓ DO menerapkan Pola Pembuangan Dasar pada jenis yang berisi instans jenis sekali pakai. Lihat bagian Pola Pembuangan Dasar untuk detail tentang pola dasar.
Jika suatu tipe bertanggung jawab atas umur pakai objek yang dapat dibuang lainnya, pengembang juga memerlukan cara untuk mengelolanya. Menggunakan metode kontainer Dispose adalah cara yang mudah untuk memungkinkan hal ini.
✓ DO menerapkan Pola Pembuangan Dasar dan menyediakan finalizer pada jenis yang menyimpan sumber daya yang perlu dibebaskan secara eksplisit dan yang tidak memiliki finalizer.
Misalnya, pola harus diimplementasikan pada jenis yang menyimpan buffer memori yang tidak dikelola. Bagian Jenis yang Dapat Diselesaikan membahas pedoman yang terkait dengan penerapan finalizer.
✓ PERTIMBANGKAN untuk menerapkan Pola Pembuangan Dasar pada kelas yang tidak menyimpan sumber daya yang tidak dikelola atau objek sekali pakai tetapi kemungkinan memiliki subjenis yang melakukannya.
Contoh yang bagus dari ini adalah kelas System.IO.Stream. Meskipun ini adalah kelas dasar abstrak yang tidak menyimpan sumber daya apa pun, sebagian besar subkelasnya melakukan dan karenanya, itu menerapkan pola ini.
Pola Pembuangan Dasar
Implementasi dasar pola melibatkan penerapan System.IDisposable antarmuka dan mendeklarasikan Dispose(bool) metode yang mengimplementasikan semua logika pembersihan sumber daya untuk dibagikan antara Dispose metode dan finalizer opsional.
Contoh berikut menunjukkan implementasi sederhana dari pola dasar:
public class DisposableResourceHolder : IDisposable {
private SafeHandle resource; // handle to a resource
public DisposableResourceHolder() {
this.resource = ... // allocates the resource
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing) {
if (disposing) {
if (resource!= null) resource.Dispose();
}
}
}
Parameter Boolean disposing menunjukkan apakah metode dipanggil dari IDisposable.Dispose implementasi atau dari finalizer. Implementasi Dispose(bool) harus memeriksa parameter sebelum mengakses objek referensi lain (misalnya, bidang sumber daya dalam sampel sebelumnya). Objek tersebut hanya boleh diakses ketika metode dipanggil dari IDisposable.Dispose implementasi (ketika disposing parameter sama dengan true). Jika metode dipanggil dari finalizer (disposing salah), objek lain tidak boleh diakses. Alasannya adalah bahwa objek diselesaikan dalam urutan yang tidak dapat diprediksi sehingga objek atau dependensinya mungkin sudah diselesaikan.
Selain itu, bagian ini berlaku untuk kelas dengan dasar yang belum menerapkan Pola Dispose. Jika Anda mewarisi dari kelas yang sudah menerapkan pola, cukup gantikan metode Dispose(bool) untuk menyediakan logika tambahan dalam pembersihan sumber daya.
✓ DO mendeklarasikan protected virtual void Dispose(bool disposing) metode untuk memusatkan semua logika yang terkait dengan melepaskan sumber daya yang tidak dikelola.
Semua pembersihan sumber daya harus terjadi dalam metode ini. Metode ini dipanggil dari finalizer dan metode IDisposable.Dispose. Parameter akan salah jika dipanggil dari dalam finalizer. Ini harus digunakan untuk memastikan kode apa pun yang berjalan selama finalisasi tidak mengakses objek lain yang dapat diselesaikan. Detail penerapan finalizer dijelaskan di bagian berikutnya.
protected virtual void Dispose(bool disposing) {
if (disposing) {
if (resource!= null) resource.Dispose();
}
}
✓ DO mengimplementasikan IDisposable antarmuka hanya dengan memanggil Dispose(true) diikuti oleh GC.SuppressFinalize(this).
Panggilan ke SuppressFinalize seharusnya hanya terjadi jika Dispose(true) berhasil dijalankan.
public void Dispose(){
Dispose(true);
GC.SuppressFinalize(this);
}
X JANGAN membuat metode tanpa parameter Dispose virtual.
Metode Dispose(bool) ini adalah metode yang harus dioverride oleh subkelas.
// bad design
public class DisposableResourceHolder : IDisposable {
public virtual void Dispose() { ... }
protected virtual void Dispose(bool disposing) { ... }
}
// good design
public class DisposableResourceHolder : IDisposable {
public void Dispose() { ... }
protected virtual void Dispose(bool disposing) { ... }
}
X JANGAN menyatakan kelebihan beban Dispose metode selain Dispose() dan Dispose(bool).
Dispose harus dianggap sebagai kata khusus untuk membantu mengkodifikasi pola ini dan mencegah kebingungan di antara pelaksana, pengguna, dan kompilator. Beberapa bahasa mungkin memilih untuk menerapkan pola ini secara otomatis pada jenis tertentu.
✓ DO memungkinkan Dispose(bool) metode untuk dipanggil lebih dari sekali. Metode ini mungkin memilih untuk tidak melakukan apa pun setelah panggilan pertama.
public class DisposableResourceHolder : IDisposable {
bool disposed = false;
protected virtual void Dispose(bool disposing) {
if (disposed) return;
// cleanup
...
disposed = true;
}
}
X AVOID menghasilkan pengecualian dari dalam Dispose(bool) kecuali dalam situasi kritis di mana proses yang terlibat telah rusak (kebocoran, status bersama yang tidak konsisten, dan lain-lain).
Pengguna mengharapkan bahwa panggilan ke Dispose tidak akan menimbulkan pengecualian.
Jika Dispose dapat memunculkan pengecualian, logika pembersihan blok akhirnya tidak akan dijalankan lebih lanjut. Untuk mengatasi hal ini, pengguna harus membungkus setiap panggilan ke Dispose (dalam blok akhirnya!) dalam blok percobaan, yang mengarah ke handler pembersihan yang sangat kompleks. Jika menjalankan metode Dispose(bool disposing), jangan pernah melemparkan pengecualian jika pembuangan tidak dilakukan. Melakukannya akan mengakhiri proses jika dijalankan di dalam konteks finalizer.
✓ DO lemparkan ObjectDisposedException dari anggota mana pun yang tidak dapat digunakan setelah objek dihapus.
public class DisposableResourceHolder : IDisposable {
bool disposed = false;
SafeHandle resource; // handle to a resource
public void DoSomething() {
if (disposed) throw new ObjectDisposedException(...);
// now call some native methods using the resource
...
}
protected virtual void Dispose(bool disposing) {
if (disposed) return;
// cleanup
...
disposed = true;
}
}
✓ PERTIMBANGKAN menyediakan metode Close(), selain Dispose(), jika penutupan adalah istilah standar di area tersebut.
Ketika melakukannya, penting bahwa Anda membuat implementasi Close identik dengan Dispose dan mempertimbangkan untuk menerapkan metode IDisposable.Dispose secara eksplisit.
public class Stream : IDisposable {
IDisposable.Dispose() {
Close();
}
public void Close() {
Dispose(true);
GC.SuppressFinalize(this);
}
}
Tipe yang Dapat Di-finalisasi
Jenis yang dapat diselesaikan memperluas Pola Pembuangan Dasar dengan menimpa finalizer dan menyediakan jalur kode finalisasi dalam metode Dispose(bool).
Finalizer terkenal sulit diterapkan dengan benar, terutama karena Anda tidak dapat memastikan asumsi (biasanya valid) tentang status sistem dalam pelaksanaannya. Pedoman berikut harus dipertimbangkan dengan cermat.
Perhatikan bahwa beberapa pedoman tidak hanya berlaku untuk Finalize metode , tetapi untuk kode apa pun yang dipanggil dari finalizer. Dalam kasus Pola Pembuangan Dasar yang telah ditentukan sebelumnya, ini berarti logika yang dieksekusi di dalam blok Dispose(bool disposing) ketika parameter disposing bernilai false.
Jika kelas dasar sudah dapat difinalisasi dan mengimplementasikan Basic Dispose Pattern, Anda tidak boleh mengoverride Finalize lagi. Anda sebaiknya meng-override metode Dispose(bool) untuk menyediakan logika tambahan pembersihan sumber daya.
Kode berikut menunjukkan contoh jenis yang dapat diselesaikan:
public class ComplexResourceHolder : IDisposable {
private IntPtr buffer; // unmanaged memory buffer
private SafeHandle resource; // disposable handle to a resource
public ComplexResourceHolder() {
this.buffer = ... // allocates memory
this.resource = ... // allocates the resource
}
protected virtual void Dispose(bool disposing) {
ReleaseBuffer(buffer); // release unmanaged memory
if (disposing) { // release other disposable objects
if (resource!= null) resource.Dispose();
}
}
~ComplexResourceHolder() {
Dispose(false);
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
}
X AVOID membuat jenis dapat diselesaikan.
Pertimbangkan dengan cermat setiap kasus di mana Anda merasa perlu menggunakan sebuah finalizer. Ada biaya nyata yang terkait dengan instans dengan finalizer, dari sudut performa dan kompleksitas kode. Lebih suka menggunakan pembungkus sumber daya seperti SafeHandle untuk merangkum sumber daya yang tidak dikelola jika memungkinkan, dalam hal ini finalizer menjadi tidak perlu karena pembungkus bertanggung jawab atas pembersihan sumber dayanya sendiri.
X JANGAN membuat jenis nilai dapat di-finalisasi.
Hanya tipe referensi yang benar-benar di-finalisasi oleh CLR, dan dengan demikian setiap upaya untuk menempatkan finalizer pada tipe nilai akan diabaikan. Pengkompilasi C# dan C++ memberlakukan aturan ini.
✓ DO jadikan tipe dapat difinalisasi jika tipe tersebut bertanggung jawab untuk merilis sumber daya tak dikelola yang tidak memiliki finalizer sendiri.
Saat mengimplementasikan finalizer, cukup panggil Dispose(false) dan tempatkan semua logika pembersihan sumber daya dalam metode Dispose(bool disposing).
public class ComplexResourceHolder : IDisposable {
~ComplexResourceHolder() {
Dispose(false);
}
protected virtual void Dispose(bool disposing) {
...
}
}
✓ DO menerapkan Pola Dispose Dasar pada setiap tipe yang dapat diakhirkan.
Ini memberi pengguna jenis sarana untuk secara eksplisit melakukan pembersihan deterministik dari sumber daya yang sama di mana finalizer bertanggung jawab.
X JANGAN mengakses objek yang dapat diselesaikan di jalur kode finalizer, karena ada risiko signifikan bahwa objek tersebut akan telah diselesaikan.
Misalnya, objek yang dapat diselesaikan A yang memiliki referensi ke objek lain yang dapat diselesaikan B tidak dapat menggunakan B dengan andal di finalizer A, atau sebaliknya. Finalizer dipanggil dalam urutan tidak menentu (tidak adanya jaminan urutan lemah untuk finalisasi kritis).
Selain itu, ketahuilah bahwa objek yang disimpan dalam variabel statis akan dikumpulkan di titik-titik tertentu selama pembongkaran domain aplikasi atau saat keluar dari proses. Mengakses variabel statis yang mengacu pada objek yang dapat diselesaikan (atau memanggil metode statis yang mungkin menggunakan nilai yang disimpan dalam variabel statis) mungkin tidak aman jika Environment.HasShutdownStarted mengembalikan true.
✓ DO membuat metode Anda Finalize terlindungi.
Pengembang C#, C++, dan VB.NET tidak perlu khawatir tentang hal ini, karena kompilator membantu menegakkan pedoman ini.
X JANGAN biarkan pengecualian lolos dari logika finalizer, kecuali untuk kegagalan kritis sistem.
Jika pengecualian dilemparkan dari finalizer, CLR akan mematikan seluruh proses (pada .NET Framework versi 2.0), mencegah finalizer lain mengeksekusi dan sumber daya dilepaskan dengan cara yang terkontrol.
✓ PERTIMBANGKAN untuk membuat dan menggunakan objek kritis yang bersifat finalizable (jenis dengan jenjang yang berisi CriticalFinalizerObject) untuk situasi di mana finalizer benar-benar harus dijalankan meskipun menghadapi penghentian paksa domain aplikasi dan pembatalan thread.
© Porsi 2005, 2009 Microsoft Corporation. Seluruh hak cipta dilindungi.
Dicetak ulang oleh izin Pearson Education, Inc. dari Panduan Desain Kerangka Kerja: Konvensi, Idiom, dan Pola untuk Pustaka .NET yang Dapat Digunakan Kembali, Edisi ke-2 oleh Krzysztof Cwalina dan Brad Abrams, diterbitkan 22 Okt 2008 oleh Addison-Wesley Professional sebagai bagian dari Seri Pengembangan Microsoft Windows.