Implementasi IDisposable yang benar
Antarmuka yang digunakan untuk melepaskan sumber daya objek di .NET adalah IDisposable, tetapi implementasi antarmuka ini cukup khusus. Selain itu, terdapat dua fungsi: Finalisasi dan Tutup.
MSDN merekomendasikan penerapan antarmuka IDisposable sesuai dengan pola berikut:
1 kelas publik Foo: IDisposable
2 {
3 kekosongan publik Buang()
4 {
5 Buang (benar);
6 GC.SuppressFinalize(ini);
7}
8
9 virtual void yang dilindungi Buang (pembuangan bool)
10 {
11 jika (!m_disposisi)
12 {
13 jika (membuang)
14 {
15 // Rilis sumber daya yang dikelola
16}
17
18 // Melepaskan sumber daya yang tidak dikelola
19
20 m_disposisi = benar;
dua puluh satu }
dua puluh dua }
dua puluh tiga
24 ~Bodoh()
25 {
26 Buang (salah);
27}
28
29 bool pribadi m_dispose;
30}
31
32
Sebenarnya ada dua fungsi untuk melepaskan sumber daya di objek .NET: Buang dan Selesaikan. Tujuan Finalisasi adalah untuk melepaskan sumber daya yang tidak dikelola, sedangkan Buang digunakan untuk melepaskan semua sumber daya, termasuk yang dikelola dan tidak dikelola.
Dalam mode ini, fungsi void Dispose(bool dispose) menggunakan parameter disposisi untuk membedakan apakah fungsi tersebut sedang dipanggil oleh Dispose(). Jika dipanggil dengan Dispose(), sumber daya yang dikelola dan tidak dikelola harus dilepaskan secara bersamaan. Jika dipanggil oleh ~Foo() (yaitu, Finalize() C#), maka Anda hanya perlu melepaskan sumber daya yang tidak dikelola.
Hal ini karena fungsi Dispose() secara eksplisit dipanggil oleh kode lain dan memerlukan pelepasan sumber daya, sedangkan Finalize dipanggil oleh GC. Objek terkelola lainnya yang dirujuk oleh Foo mungkin tidak perlu dimusnahkan saat GC dipanggil, dan meskipun dihancurkan, objek tersebut akan dipanggil oleh GC. Oleh karena itu, hanya sumber daya yang tidak dikelola yang perlu dilepaskan di Finalisasi. Di sisi lain, karena sumber daya yang dikelola dan tidak dikelola telah dirilis di Dispose(), maka Finalize tidak perlu dipanggil lagi saat objek didaur ulang oleh GC, jadi panggil GC.SuppressFinalize(this) di Dispose() untuk menghindari duplikasi Panggilan Menyelesaikan.
Namun, tidak ada masalah meskipun Finalisasi dan Buang dipanggil berulang kali, karena keberadaan variabel m_dispose, sumber daya hanya akan dilepaskan satu kali, dan panggilan berlebihan akan diabaikan.
Oleh karena itu, pola di atas menjamin:
1. Selesaikan hanya rilis sumber daya yang tidak dikelola;
2. Buang rilis sumber daya yang dikelola dan tidak dikelola;
3. Tidak ada masalah dalam memanggil Finalisasi dan Buang berulang kali;
4. Finalisasi dan Buang memiliki strategi pelepasan sumber daya yang sama, sehingga tidak ada konflik di antara keduanya .
Dalam C#, pola ini perlu diterapkan secara eksplisit, di mana fungsi ~Foo() C# mewakili Finalize(). Di C++/CLI, mode ini diimplementasikan secara otomatis, tetapi destruktor kelas C++ berbeda.
Menurut semantik C++, destruktor dipanggil ketika ia keluar dari cakupan atau dihapus. Di Managed C++ (yaitu, C++ terkelola di .NET 1.1), destruktor setara dengan metode Finalize() di CLR, yang dipanggil oleh GC selama pengumpulan sampah. Dalam C++/CLI di .NET 2.0, semantik destruktor dimodifikasi agar setara dengan metode Dispose(), yang menyiratkan dua hal:
1. Semua kelas CLR di C++/CLI mengimplementasikan antarmuka IDisposable, sehingga Anda dapat menggunakan kata kunci penggunaan untuk mengakses instance kelas ini di C#.
2. Destruktor tidak lagi setara dengan Finalize().
Untuk poin pertama, ini adalah hal yang baik, menurut saya secara semantik Dispose() lebih dekat dengan destruktor C++. Mengenai poin kedua, Microsoft telah membuat ekstensi dengan memperkenalkan fungsi "!", seperti yang ditunjukkan di bawah ini:
1 public ref class Foo
2 {
3 publik:
4Foo();
5 ~Foo(); // penghancur
6 !Foo(); // penyelesaian
7};
8
Fungsi "!" (Saya benar-benar tidak tahu harus menyebutnya apa) menggantikan Finalize() asli di Managed C++ dan dipanggil oleh GC. MSDN merekomendasikan untuk mengurangi duplikasi kode, Anda dapat menulis kode seperti ini:
1 ~Foo()
2 {
3 //Melepaskan sumber daya yang dikelola
4 ini->!Foo();
5}
6
7 !Foo()
8 {
9 //Lepaskan sumber daya yang tidak dikelola
10}
11
Untuk kelas di atas, kode C# terkait yang dihasilkan oleh C++/CLI sebenarnya adalah sebagai berikut:
1 kelas publik Foo
2 {
3 kekosongan pribadi !Foo()
4 {
5 // Melepaskan sumber daya yang tidak dikelola
6}
7
8 kekosongan pribadi ~Foo()
9 {
10 // Rilis sumber daya yang dikelola
11 !Foo();
12}
13
14 publikFoo()
15 {
16}
17
18 kekosongan publik Buang()
19 {
20 Buang (benar);
21 GC.SuppressFinalize(ini);
dua puluh dua }
dua puluh tiga
24 virtual void yang dilindungi Buang (pembuangan bool)
25 {
26 jika (membuang)
27 {
28 ~Foo();
29 }
30 lainnya
31 {
32 mencoba
33 {
34 !Foo();
35}
36 akhirnya
37 {
38 basis. Finalisasi();
39 }
40}
41 }
42
43 batal terlindung Finalisasi()
44 {
45 Buang (salah);
46 }
47 }
48
Karena ~Foo() dan !Foo() tidak akan dipanggil berulang kali (setidaknya menurut MS), tidak ada variabel dalam kode ini yang sama dengan m_disposition sebelumnya, tetapi struktur dasarnya sama.
Selain itu, Anda dapat melihat bahwa sebenarnya bukan ~Foo() dan !Foo() yang merupakan Dispose dan Finalize, namun compiler C++/CLI menghasilkan dua fungsi Dispose dan Finalize dan memanggilnya pada waktu yang tepat. C++/CLI sebenarnya telah melakukan banyak pekerjaan, tetapi satu-satunya masalah adalah ia bergantung pada panggilan pengguna !Foo() di ~Foo().
Mengenai pelepasan sumber daya, hal terakhir yang disebutkan adalah fungsi Tutup. Secara semantik sangat mirip dengan Dispose. Menurut MSDN, fungsi ini disediakan untuk membuat pengguna merasa lebih nyaman, karena pengguna lebih terbiasa memanggil Close() untuk objek tertentu, misalnya file.
Namun, kedua fungsi ini melakukan hal yang sama, sehingga kode yang direkomendasikan oleh MSDN adalah:
1 kekosongan publik Tutup()
2 {
3 Buang(();
4}
5
6
Di sini, fungsi Dispose tanpa parameter dipanggil secara langsung untuk mendapatkan semantik yang sama dengan Dispose. Tampaknya ini sempurna, namun sebaliknya, jika Dispose dan Close disediakan secara bersamaan, hal ini akan menimbulkan kebingungan bagi pengguna. Tanpa melihat detail kodenya, sulit mengetahui perbedaan kedua fungsi ini. Oleh karena itu, spesifikasi desain kode .NET menyatakan bahwa pengguna sebenarnya hanya dapat menggunakan salah satu dari dua fungsi ini. Jadi pola yang disarankan adalah:
1 public class Foo: IDisposable
2 {
3 kekosongan publik Tutup()
4 {
5 Buang();
6}
7
8 batal IDisposable.Buang()
9 {
10 Buang (benar);
11 GC.SuppressFinalize(ini);
12}
13
14 virtual void yang dilindungi Buang (pembuangan bool)
15 {
16 // Sama seperti sebelumnya
17}
18}
19
Apa yang disebut implementasi antarmuka eksplisit digunakan di sini: void IDisposable.Dispose(). Implementasi eksplisit ini hanya dapat diakses melalui antarmuka, namun tidak melalui kelas pelaksana. Karena itu:
1 Foo foo = Foo baru();
2
3 foo.Buang(); // Kesalahan
4 (foo sebagai IDisposable).Dispose(); // Benar
5
Ini menangani keduanya. Bagi yang suka menggunakan Close, bisa langsung menggunakan foo.Close(), dan tidak akan melihat Dispose(). Bagi yang suka Dispose, dia bisa mengubah tipenya menjadi IDisposable untuk memanggil, atau menggunakan pernyataan penggunaan. Sukacita yang luar biasa untuk keduanya!
http://www.cnblogs.com/xlshcn/archive/2007/01/16/idisposable.html