Penggunaan antarmuka IDisposable yang tepat

1659

Saya tahu dari membaca dokumentasi Microsoft bahwa "utama" penggunaan IDisposableantarmuka adalah untuk membersihkan sumber daya yang tidak dikelola.

Bagi saya, "tidak terkelola" berarti hal-hal seperti koneksi basis data, soket, pegangan jendela, dll. Tapi, saya telah melihat kode di mana Dispose()metode ini diterapkan untuk sumber daya yang dikelola gratis , yang tampaknya berlebihan bagi saya, karena pengumpul sampah harus mengurus itu untukmu

Sebagai contoh:

public class MyCollection : IDisposable
{
    private List<String> _theList = new List<String>();
    private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();

    // Die, clear it up! (free unmanaged resources)
    public void Dispose()
    {
        _theList.clear();
        _theDict.clear();
        _theList = null;
        _theDict = null;
    }

Pertanyaan saya adalah, apakah ini membuat pemulung bebas memori yang digunakan MyCollectionlebih cepat dari biasanya?

sunting : Sejauh ini orang telah memposting beberapa contoh penggunaan IDisposable yang baik untuk membersihkan sumber daya yang tidak dikelola seperti koneksi basis data dan bitmap. Tapi anggaplah bahwa _theListdalam kode di atas berisi sejuta string, dan Anda ingin membebaskan memori itu sekarang , daripada menunggu pengumpul sampah. Apakah kode di atas mencapai itu?

cwick
sumber
34
Saya suka jawaban yang diterima karena memberi tahu Anda 'pola' yang benar menggunakan IDisposable, tetapi seperti kata OP dalam editannya, itu tidak menjawab pertanyaan yang dimaksud. IDisposable tidak 'memanggil' GC, itu hanya 'menandai' objek yang dapat dihancurkan. Tapi apa cara nyata untuk membebaskan memori 'sekarang' daripada menunggu GC untuk menendang? Saya pikir pertanyaan ini patut dibahas lebih lanjut.
Punit Vora
40
IDisposabletidak menandai apa pun. The DisposeMetode melakukan apa yang harus dilakukan untuk membersihkan sumber daya yang digunakan oleh instance. Ini tidak ada hubungannya dengan GC.
John Saunders
4
@ John Saya mengerti IDisposable. Dan itulah sebabnya saya mengatakan bahwa jawaban yang diterima tidak menjawab pertanyaan yang dimaksud OP (dan tindak lanjut edit) tentang apakah IDisposable akan membantu dalam <i> membebaskan memori </i>. Karena IDisposabletidak ada hubungannya dengan membebaskan memori, hanya sumber daya, maka seperti yang Anda katakan, tidak perlu mengatur referensi yang dikelola menjadi nol sama sekali seperti yang dilakukan OP dalam contohnya. Jadi, jawaban yang benar untuk pertanyaannya adalah "Tidak, itu tidak membantu membebaskan memori lebih cepat. Bahkan, itu sama sekali tidak membantu membebaskan memori, hanya sumber daya". Tapi bagaimanapun, terima kasih atas masukan Anda.
Punit Vora
9
@desigeek: jika ini masalahnya, maka Anda seharusnya tidak mengatakan "IDisposable tidak 'memanggil' GC, itu hanya 'menandai' objek yang dapat dihancurkan"
John Saunders
5
@desigeek: Tidak ada cara dijamin untuk membebaskan memori secara deterministik. Anda bisa menghubungi GC.Collect (), tapi itu permintaan yang sopan, bukan permintaan. Semua utas yang berjalan harus ditangguhkan agar pengumpulan sampah dapat dilanjutkan - baca konsep .NET safepoints jika Anda ingin mempelajari lebih lanjut, mis. Msdn.microsoft.com/en-us/library/678ysw69(v=vs.110). aspx . Jika utas tidak dapat ditangguhkan, misalnya karena ada panggilan ke kode yang tidak dikelola, GC.Collect () mungkin tidak melakukan apa pun.
Concrete Gannet

Jawaban:

2609

Tujuan Buang adalah membebaskan sumber daya yang tidak dikelola. Itu perlu dilakukan di beberapa titik, kalau tidak mereka tidak akan pernah dibersihkan. Pengumpul sampah tidak tahu cara memanggil DeleteHandle()variabel tipe IntPtr, tidak tahu apakah perlu memanggil atau tidak DeleteHandle().

Catatan : Apa itu sumber daya yang tidak dikelola ? Jika Anda menemukannya di Microsoft .NET Framework: ini dikelola. Jika Anda pergi sendiri di MSDN, itu tidak dikelola. Apa pun yang Anda gunakan untuk P / Panggil panggilan untuk keluar dari dunia nyaman yang bagus dari semua yang tersedia untuk Anda di .NET Framework tidak dikelola - dan sekarang Anda bertanggung jawab untuk membersihkannya.

Objek yang Anda buat perlu mengekspos beberapa metode, yang dapat dipanggil oleh dunia luar, untuk membersihkan sumber daya yang tidak dikelola. Metode ini dapat dinamai apa pun yang Anda suka:

public void Cleanup()

atau

public void Shutdown()

Tetapi sebaliknya ada nama standar untuk metode ini:

public void Dispose()

Bahkan ada antarmuka yang dibuat IDisposable,, yang hanya memiliki satu metode:

public interface IDisposable
{
   void Dispose()
}

Jadi Anda membuat objek Anda mengekspos IDisposableantarmuka, dan dengan cara itu Anda berjanji bahwa Anda telah menulis metode tunggal untuk membersihkan sumber daya yang tidak dikelola:

public void Dispose()
{
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}

Dan kamu sudah selesai. Kecuali Anda bisa berbuat lebih baik.


Bagaimana jika objek Anda telah mengalokasikan System.Drawing.Bitmap 250MB (mis. Kelas Bitmap yang dikelola .NET) sebagai semacam bingkai penyangga? Tentu, ini adalah objek .NET yang dikelola, dan pengumpul sampah akan membebaskannya. Tapi apakah Anda benar-benar ingin meninggalkan memori 250MB hanya duduk di sana - menunggu pengumpul sampah akhirnya datang dan membebaskannya? Bagaimana jika ada koneksi database terbuka ? Tentunya kita tidak ingin koneksi itu terbuka, menunggu GC menyelesaikan objek.

Jika pengguna telah menelepon Dispose()(artinya mereka tidak lagi berencana untuk menggunakan objek) mengapa tidak menyingkirkan bitmap yang boros dan koneksi database?

Jadi sekarang kita akan:

  • menyingkirkan sumber daya yang tidak dikelola (karena kita harus), dan
  • singkirkan sumber daya yang dikelola (karena kami ingin membantu)

Jadi, mari perbarui Dispose()metode kami untuk menyingkirkan objek yang dikelola tersebut:

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose();
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose();
      this.frameBufferImage = null;
   }
}

Dan semuanya baik-baik saja, kecuali Anda bisa berbuat lebih baik !


Bagaimana jika orang tersebut lupa memanggil Dispose()objek Anda? Kemudian mereka akan membocorkan beberapa sumber daya yang tidak dikelola !

Catatan: Mereka tidak akan membocorkan sumber daya yang dikelola , karena pada akhirnya pengumpul sampah akan berjalan, di utas latar belakang, dan membebaskan memori yang terkait dengan objek yang tidak digunakan. Ini akan termasuk objek Anda, dan semua objek terkelola yang Anda gunakan (misalnya, Bitmapdan DbConnection).

Jika orang itu lupa menelepon Dispose(), kita masih bisa menyimpan bacon mereka! Kami masih memiliki cara untuk menyebutnya untuk mereka: ketika pengumpul sampah akhirnya berhasil membebaskan (yaitu menyelesaikan) objek kami.

Catatan: Pengumpul sampah pada akhirnya akan membebaskan semua objek yang dikelola. Ketika itu terjadi, ia memanggil Finalize metode pada objek. GC tidak tahu, atau peduli, tentang metode Buang Anda . Itu hanya nama yang kami pilih untuk metode yang kami sebut ketika kami ingin menyingkirkan hal-hal yang tidak dikelola.

Penghancuran objek kita oleh pengumpul Sampah adalah waktu yang tepat untuk membebaskan sumber daya tak terkelola yang sial itu. Kami melakukan ini dengan mengganti Finalize()metode.

Catatan: Di C #, Anda tidak secara eksplisit menimpa Finalize()metode ini. Anda menulis sebuah metode yang terlihat seperti sebuah C ++ destructor , dan compiler mengambil bahwa untuk menjadi implementasi dari Finalize()metode:

~MyObject()
{
    //we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
    Dispose(); //<--Warning: subtle bug! Keep reading!
}

Tetapi ada bug dalam kode itu. Anda lihat, pengumpul sampah berjalan di utas latar belakang ; Anda tidak tahu urutan dua objek dihancurkan. Sangat mungkin bahwa dalam Dispose()kode Anda , objek terkelola yang Anda coba singkirkan (karena Anda ingin membantu) sudah tidak ada lagi:

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
      this.frameBufferImage = null;
   }
}

Jadi yang Anda butuhkan adalah cara untuk Finalize()mengatakan Dispose()bahwa itu tidak boleh menyentuh sumber daya yang dikelola (karena mereka mungkin tidak ada lagi), sementara masih membebaskan sumber daya yang tidak dikelola.

Pola standar untuk melakukan ini adalah memiliki Finalize()dan Dispose()keduanya memanggil metode ketiga (!); tempat Anda menyampaikan pepatah Boolean jika Anda memanggilnya dari Dispose()(berlawanan dengan Finalize()), artinya aman untuk sumber daya yang dikelola gratis.

Metode internal ini dapat diberi beberapa nama arbitrer seperti "CoreDispose", atau "MyInternalDispose", tetapi adalah tradisi untuk menyebutnya Dispose(Boolean):

protected void Dispose(Boolean disposing)

Tetapi nama parameter yang lebih bermanfaat mungkin:

protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too, but only if I'm being called from Dispose
   //(If I'm being called from Finalize then the objects might not exist
   //anymore
   if (itIsSafeToAlsoFreeManagedObjects)  
   {    
      if (this.databaseConnection != null)
      {
         this.databaseConnection.Dispose();
         this.databaseConnection = null;
      }
      if (this.frameBufferImage != null)
      {
         this.frameBufferImage.Dispose();
         this.frameBufferImage = null;
      }
   }
}

Dan Anda mengubah penerapan IDisposable.Dispose()metode ini menjadi:

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
}

dan finalizer Anda ke:

~MyObject()
{
   Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}

Catatan : Jika objek Anda turun dari objek yang mengimplementasikan Dispose, maka jangan lupa untuk memanggil basisnya Buang metode ketika Anda menimpa Buang:

public override void Dispose()
{
    try
    {
        Dispose(true); //true: safe to free managed resources
    }
    finally
    {
        base.Dispose();
    }
}

Dan semuanya baik-baik saja, kecuali Anda bisa berbuat lebih baik !


Jika pengguna memanggil Dispose()objek Anda, maka semuanya telah dibersihkan. Kemudian, ketika pemulung datang dan memanggil Selesai, ia akan memanggil Disposelagi.

Tidak hanya boros, tetapi jika objek Anda memiliki referensi sampah ke objek yang sudah Anda buang dari panggilan terakhirDispose() , Anda akan mencoba membuangnya lagi!

Anda akan melihat dalam kode saya, saya berhati-hati untuk menghapus referensi ke objek yang telah saya buang, jadi saya tidak mencoba untuk memanggil Disposereferensi objek sampah. Tapi itu tidak menghentikan bug halus masuk.

Ketika pengguna memanggil Dispose(): pegangan CursorFileBitmapIconServiceHandle dihancurkan. Kemudian ketika pengumpul sampah berjalan, ia akan mencoba untuk menghancurkan pegangan yang sama lagi.

protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy 
   ...
}

Cara Anda memperbaiki ini adalah memberi tahu pengumpul sampah bahwa tidak perlu repot menyelesaikan objek - sumber dayanya sudah dibersihkan, dan tidak ada lagi pekerjaan yang diperlukan. Anda melakukan ini dengan memanggil GC.SuppressFinalize()dalam Dispose()metode:

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
   GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}

Sekarang setelah pengguna menelepon Dispose(), kami memiliki:

  • membebaskan sumber daya yang tidak dikelola
  • membebaskan sumber daya yang dikelola

Tidak ada gunanya dalam GC menjalankan finalizer - semuanya diurus.

Tidak bisakah saya menggunakan Finalisasi untuk membersihkan sumber daya yang tidak dikelola?

Dokumentasi untuk Object.Finalizemengatakan:

Metode Finalisasi digunakan untuk melakukan operasi pembersihan pada sumber daya yang tidak dikelola yang dimiliki oleh objek saat ini sebelum objek dihancurkan.

Tetapi dokumentasi MSDN juga mengatakan, untuk IDisposable.Dispose:

Melakukan tugas yang ditentukan aplikasi yang terkait dengan membebaskan, melepaskan, atau mengatur ulang sumber daya yang tidak dikelola.

Jadi yang mana? Yang mana tempat bagi saya untuk membersihkan sumber daya yang tidak dikelola? Jawabannya adalah:

Itu pilihanmu! Tapi pilih Dispose.

Anda tentu dapat menempatkan pembersihan yang tidak dikelola di finalizer:

~MyObject()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //A C# destructor automatically calls the destructor of its base class.
}

Masalahnya adalah Anda tidak tahu kapan pemulung akan menyelesaikan objek Anda. Sumber daya asli Anda yang tidak dikelola, tidak dibutuhkan, dan tidak digunakan akan tetap ada sampai pemulung akhirnya berjalan. Maka itu akan memanggil metode finalizer Anda; membersihkan sumber daya yang tidak dikelola. Dokumentasi Object.Finalize menunjukkan ini:

Waktu yang tepat ketika finalizer dieksekusi tidak ditentukan. Untuk memastikan pelepasan sumber daya deterministik untuk instance kelas Anda, terapkan metode Tutup atau sediakan IDisposable.Disposeimplementasi.

Ini adalah keutamaan menggunakan Disposeuntuk membersihkan sumber daya yang tidak dikelola; Anda mengenal, dan mengontrol, ketika sumber daya yang tidak dikelola dibersihkan. Kehancuran mereka "deterministik" .


Untuk menjawab pertanyaan awal Anda: Mengapa tidak melepaskan memori sekarang, daripada ketika GC memutuskan untuk melakukannya? Saya memiliki perangkat lunak pengenal wajah yang perlu menyingkirkan 530 MB gambar internal sekarang , karena mereka tidak lagi diperlukan. Ketika kita tidak melakukannya: mesin terhenti.

Pembacaan Bonus

Bagi siapa pun yang menyukai gaya jawaban ini (menjelaskan mengapa , bagaimana caranya menjadi jelas), saya sarankan Anda membaca Bab Satu dari COM Essential Don Box:

Dalam 35 halaman dia menjelaskan masalah menggunakan objek biner, dan menemukan COM di depan mata Anda. Setelah Anda menyadari mengapa COM, 300 halaman sisanya jelas, dan hanya merinci implementasi Microsoft.

Saya pikir setiap programmer yang pernah berurusan dengan objek atau COM harus, paling tidak, membaca bab pertama. Itu adalah penjelasan terbaik dari apa pun.

Bacaan Bonus Ekstra

Ketika semua yang Anda tahu salah oleh Eric Lippert

Karenanya memang sangat sulit untuk menulis finalizer yang benar, dan saran terbaik yang dapat saya berikan kepada Anda adalah untuk tidak mencoba .

Ian Boyd
sumber
12
Anda dapat melakukannya dengan lebih baik - Anda perlu menambahkan panggilan ke GC.SuppressFinalize () di Buang.
plinth
55
@ Daniel Earwicker: Itu benar. Microsoft akan senang bagi Anda untuk berhenti menggunakan Win32 sama sekali, dan tetap menggunakan abstrak. Portable, panggilan .NET Framework panggilan. Jika Anda ingin melihat-lihat sistem operasi di bawahnya; karena Anda pikir Anda tahu apa OS berjalan: Anda mengambil hidup Anda di tangan Anda sendiri. Tidak semua aplikasi .NET berjalan di Windows, atau di desktop.
Ian Boyd
34
Ini adalah jawaban yang bagus tetapi saya pikir itu akan mendapat manfaat dari daftar kode akhir untuk kasus standar dan untuk kasus di mana kelas berasal dari kelas dasar yang sudah mengimplementasikan Buang. misal sudah baca di sini ( msdn.microsoft.com/en-us/library/aa720161%28v=vs.71%29.aspx ) dan saya juga bingung tentang apa yang harus saya lakukan ketika berasal dari kelas yang sudah mengimplementasikan Buang ( hei saya baru dalam hal ini).
integra753
5
@Regs, dan lain-lain: Umumnya saya tidak akan repot mengatur referensi null. Pertama-tama, itu berarti Anda tidak dapat membuatnya readonly, dan kedua, Anda harus melakukan !=nullpemeriksaan yang sangat jelek (seperti pada kode contoh). Anda bisa memiliki bendera disposed, tetapi lebih mudah untuk tidak menghiraukannya. .NET GC cukup agresif sehingga referensi ke suatu bidang xtidak lagi dihitung 'digunakan' saat melewati x.Dispose()garis.
porges
7
Di halaman kedua buku Don Box yang Anda sebutkan, ia menggunakan contoh implementasi O (1) dari algoritma pencarian, yang "detailnya dibiarkan sebagai latihan untuk pembaca". Saya tertawa.
hapus
65

IDisposablesering digunakan untuk mengeksploitasi usingpernyataan dan mengambil keuntungan dari cara mudah untuk melakukan pembersihan deterministik objek yang dikelola.

public class LoggingContext : IDisposable {
    public Finicky(string name) {
        Log.Write("Entering Log Context {0}", name);
        Log.Indent();
    }
    public void Dispose() {
        Log.Outdent();
    }

    public static void Main() {
        Log.Write("Some initial stuff.");
        try {
            using(new LoggingContext()) {
                Log.Write("Some stuff inside the context.");
                throw new Exception();
            }
        } catch {
            Log.Write("Man, that was a heavy exception caught from inside a child logging context!");
        } finally {
            Log.Write("Some final stuff.");
        }
    }
}
yfeldblum
sumber
6
Saya suka itu, secara pribadi, tetapi tidak benar-benar cocok dengan pedoman desain kerangka kerja.
mqp
4
Saya akan menganggapnya desain yang tepat karena memungkinkan lingkup dan pembersihan lingkup / deterministik deterministik yang mudah, terutama ketika dicampur dengan penanganan-pengecualian, penguncian, dan blok-blok sumber daya yang tidak dikelola dengan cara yang rumit. Bahasa ini menawarkan ini sebagai fitur kelas satu.
yfeldblum
Itu tidak benar-benar mengikuti aturan yang ditentukan dalam FDG tetapi jelas merupakan penggunaan pola yang valid karena diperlukan untuk digunakan oleh "menggunakan pernyataan".
Scott Dorman
2
Selama Log.Outdent tidak membuang, pasti tidak ada yang salah dengan ini.
Daniel Earwicker
1
Berbagai jawaban untuk Apakah melecehkan menggunakan IDisposable dan “menggunakan” sebagai cara untuk mendapatkan “perilaku tercakup” untuk keamanan pengecualian? masuk ke sedikit lebih detail tentang mengapa orang yang berbeda suka / tidak suka teknik ini. Agak kontroversial.
Brian
44

Tujuan dari pola Buang adalah untuk menyediakan mekanisme untuk membersihkan sumber daya yang dikelola dan tidak dikelola dan kapan itu terjadi tergantung pada bagaimana metode Buang disebut. Dalam contoh Anda, penggunaan Buang sebenarnya tidak melakukan sesuatu yang berhubungan dengan membuang, karena membersihkan daftar tidak berdampak pada koleksi yang dibuang. Demikian juga, panggilan untuk mengatur variabel ke nol juga tidak berdampak pada GC.

Anda dapat melihat artikel ini untuk detail lebih lanjut tentang cara menerapkan pola Buang, tetapi pada dasarnya terlihat seperti ini:

public class SimpleCleanup : IDisposable
{
    // some fields that require cleanup
    private SafeHandle handle;
    private bool disposed = false; // to detect redundant calls

    public SimpleCleanup()
    {
        this.handle = /*...*/;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources.
                if (handle != null)
                {
                    handle.Dispose();
                }
            }

            // Dispose unmanaged managed resources.

            disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

Metode yang paling penting di sini adalah Buang (bool), yang sebenarnya berjalan dalam dua keadaan berbeda:

  • disposing == true: metode telah dipanggil secara langsung atau tidak langsung oleh kode pengguna. Sumber daya yang dikelola dan tidak dikelola dapat dibuang.
  • membuang == false: metode ini dipanggil oleh runtime dari dalam finalizer, dan Anda tidak boleh mereferensikan objek lain. Hanya sumber daya yang tidak dikelola yang dapat dibuang.

Masalah dengan hanya membiarkan GC melakukan pembersihan adalah bahwa Anda tidak memiliki kontrol nyata atas kapan GC akan menjalankan siklus pengumpulan (Anda dapat menghubungi GC.Collect (), tetapi Anda benar-benar tidak seharusnya) sehingga sumber daya dapat tetap ada sekitar lebih lama dari yang dibutuhkan. Ingat, memanggil Buang () sebenarnya tidak menyebabkan siklus pengumpulan atau dengan cara apa pun menyebabkan GC mengumpulkan / membebaskan objek; itu hanya menyediakan sarana untuk secara lebih deterministik membersihkan sumber daya yang digunakan dan memberi tahu GC bahwa pembersihan ini telah dilakukan.

Inti dari IDisposable dan pola buang bukan tentang segera membebaskan memori. Satu-satunya waktu panggilan untuk Buang sebenarnya akan memiliki kesempatan untuk segera membebaskan memori adalah ketika menangani == skenario salah membuang dan memanipulasi sumber daya yang tidak dikelola. Untuk kode yang dikelola, memori tidak akan benar-benar direklamasi sampai GC menjalankan siklus pengumpulan, yang Anda benar-benar tidak memiliki kendali atas (selain memanggil GC.Collect (), yang telah saya sebutkan bukan ide yang baik).

Skenario Anda tidak benar-benar valid karena string. NET tidak menggunakan sumber daya yang tidak diubah dan tidak menerapkan IDisposable, tidak ada cara untuk memaksa mereka untuk "dibersihkan."

Scott Dorman
sumber
4
Bukankah Anda lupa menerapkan finalizer?
Budda
@Budda: Tidak, dia menggunakan SafeHandle. Tidak perlu destruktor.
Henk Holterman
9
+1 untuk menambahkan jaring pengaman untuk beberapa panggilan ke Buang (). Spesifikasi mengatakan beberapa panggilan harus aman. Terlalu banyak kelas Microsoft gagal menerapkannya, dan Anda mendapatkan ObjectDisposedException yang mengganggu.
Jesse Chisholm
5
Tapi Buang (membuang bool) adalah metode Anda sendiri di kelas SimpleCleanup Anda dan tidak akan pernah dipanggil oleh framework. Karena Anda hanya menyebutnya dengan "true" sebagai parameter, 'membuang' tidak akan pernah salah. Kode Anda sangat mirip dengan contoh MSDN untuk IDisposable, tetapi tidak memiliki finalizer, seperti yang ditunjukkan oleh @Budda, dari mana panggilan dengan disposing = false akan datang.
yoyo
19

Seharusnya tidak ada panggilan lebih lanjut ke metode objek setelah Buang dipanggil itu (meskipun objek harus mentolerir panggilan lebih lanjut untuk Buang). Karena itu contoh dalam pertanyaan itu konyol. Jika Buang disebut, maka objek itu sendiri dapat dibuang. Jadi pengguna harus membuang semua referensi ke seluruh objek (atur ke nol) dan semua objek yang terkait di dalamnya akan secara otomatis dibersihkan.

Adapun pertanyaan umum tentang dikelola / tidak dikelola dan diskusi dalam jawaban lain, saya pikir jawaban untuk pertanyaan ini harus dimulai dengan definisi sumber daya yang tidak dikelola.

Intinya adalah bahwa ada fungsi yang dapat Anda panggil untuk membuat sistem dalam keadaan, dan ada fungsi lain yang dapat Anda panggil untuk mengembalikannya dari keadaan itu. Sekarang, dalam contoh umum, yang pertama mungkin merupakan fungsi yang mengembalikan penanganan file, dan yang kedua mungkin menjadi panggilan CloseHandle.

Tapi - dan ini kuncinya - mereka bisa menjadi pasangan fungsi yang cocok. Yang satu membangun negara, yang lain meruntuhkannya. Jika negara telah dibangun tetapi belum dirobohkan, maka ada sumber daya yang ada. Anda harus mengatur agar teardown terjadi pada waktu yang tepat - sumber daya tidak dikelola oleh CLR. Satu-satunya jenis sumber daya yang dikelola secara otomatis adalah memori. Ada dua macam: GC, dan stack. Tipe nilai dikelola oleh stack (atau dengan menumpang tumpangan di dalam tipe referensi), dan tipe referensi dikelola oleh GC.

Fungsi-fungsi ini dapat menyebabkan perubahan status yang dapat disisipkan secara bebas, atau mungkin perlu disarangkan dengan sempurna. Perubahan status mungkin threadsafe, atau mungkin tidak.

Lihatlah contoh dalam pertanyaan Keadilan. Perubahan pada indentasi file Log harus bersarang dengan sempurna, atau semuanya salah. Juga mereka tidak mungkin threadsafe.

Dimungkinkan untuk mencari tumpangan dengan pengumpul sampah untuk membersihkan sumber daya yang tidak terkelola. Tetapi hanya jika fungsi perubahan negara adalah threadsafe dan dua negara dapat memiliki masa hidup yang tumpang tindih dengan cara apa pun. Jadi contoh keadilan dari sumber daya TIDAK harus memiliki finalizer! Itu tidak akan membantu siapa pun.

Untuk sumber daya semacam itu, Anda bisa menerapkan IDisposable, tanpa finalizer. Finalizer benar-benar opsional - harus. Ini dipoles atau bahkan tidak disebutkan dalam banyak buku.

Anda kemudian harus menggunakan usingpernyataan untuk memiliki kesempatan memastikan bahwa Disposeitu disebut. Ini pada dasarnya seperti menumpang tumpangan dengan tumpukan (jadi finalizer adalah untuk GC, usingadalah untuk tumpukan).

Bagian yang hilang adalah bahwa Anda harus menulis secara manual Buang dan buat itu memanggil bidang Anda dan kelas dasar Anda. Pemrogram C ++ / CLI tidak harus melakukan itu. Compiler menulisnya untuk mereka dalam banyak kasus.

Ada alternatif, yang saya lebih suka untuk negara-negara yang bersarang dengan sempurna dan bukan threadsafe (terlepas dari hal lain, menghindari IDisposable membuat Anda kesulitan memiliki argumen dengan seseorang yang tidak bisa menolak menambahkan finalizer ke setiap kelas yang mengimplementasikan IDisposable) .

Alih-alih menulis kelas, Anda menulis fungsi. Fungsi menerima delegasi untuk menelepon kembali ke:

public static void Indented(this Log log, Action action)
{
    log.Indent();
    try
    {
        action();
    }
    finally
    {
        log.Outdent();
    }
}

Dan kemudian contoh sederhana adalah:

Log.Write("Message at the top");
Log.Indented(() =>
{
    Log.Write("And this is indented");

    Log.Indented(() =>
    {
        Log.Write("This is even more indented");
    });
});
Log.Write("Back at the outermost level again");

Lambda yang disahkan berfungsi sebagai blok kode, jadi seperti Anda membuat struktur kontrol Anda sendiri untuk melayani tujuan yang sama dengan using, kecuali bahwa Anda tidak lagi memiliki bahaya penelepon menyalahgunakannya. Tidak mungkin mereka gagal membersihkan sumber daya.

Teknik ini kurang berguna jika sumber daya adalah jenis yang mungkin memiliki masa hidup yang tumpang tindih, karena Anda ingin dapat membangun sumber daya A, lalu sumber daya B, lalu membunuh sumber daya A dan kemudian membunuh sumber daya B. Anda tidak dapat melakukan itu jika Anda telah memaksa pengguna untuk bersarang dengan sempurna seperti ini. Tapi kemudian Anda perlu menggunakan IDisposable(tetapi masih tanpa finalizer, kecuali Anda telah mengimplementasikan threadsafety, yang tidak gratis).

Daniel Earwicker
sumber
re: "Seharusnya tidak ada panggilan lebih lanjut ke metode objek setelah Buang dipanggil". "Seharusnya" menjadi kata yang operatif. Jika Anda memiliki tindakan asinkron yang tertunda, tindakan itu dapat masuk setelah objek Anda dibuang. Menyebabkan ObjectDisposedException.
Jesse Chisholm
Tampaknya Anda adalah satu-satunya jawaban selain dari saya yang menyentuh gagasan bahwa sumber daya yang tidak dikelola merangkum pernyataan bahwa GC tidak mengerti. Namun, aspek kunci dari sumber daya yang tidak dikelola adalah bahwa satu atau lebih entitas yang keadaannya mungkin perlu dibersihkan keadaannya dapat terus eksis bahkan jika objek yang "memiliki" sumber daya itu tidak memilikinya. Bagaimana Anda menyukai definisi saya? Cukup mirip, tapi saya pikir itu membuat "sumber daya" sedikit lebih banyak kata benda (itu adalah "perjanjian" dengan objek luar untuk mengubah perilakunya, dalam pertukaran untuk pemberitahuan kapan layanannya tidak lagi diperlukan)
supercat
@supercat - jika Anda tertarik saya menulis posting berikut beberapa hari setelah saya menulis jawaban di atas: smellegantcode.wordpress.com/2009/02/13/…
Daniel Earwicker
1
@DanielEarwicker: Artikel yang menarik, meskipun saya bisa memikirkan setidaknya satu jenis sumber daya yang tidak dikelola yang Anda tidak benar-benar bahas: berlangganan acara dari objek yang berumur panjang. Langganan acara adalah sepadan, tetapi bahkan jika ingatan yang gagal tanpa batas untuk membuangnya bisa mahal. Misalnya, enumerator untuk koleksi yang memungkinkan modifikasi selama enumerasi mungkin perlu berlangganan untuk memperbarui notifikasi dari koleksi, dan koleksi mungkin diperbarui berkali-kali dalam masa pakainya. Jika enumerator ditinggalkan tanpa berhenti berlangganan ...
supercat
1
Sepasang operasi enterdan exitmerupakan inti dari bagaimana saya memikirkan sumber daya. Berlangganan / berhenti berlangganan pada acara harus sesuai dengan itu tanpa kesulitan. Dalam hal karakteristik ortogonal / sepadan itu praktis tidak bisa dibedakan dari kebocoran memori. (Ini tidak mengejutkan karena berlangganan hanya menambahkan objek ke daftar.)
Daniel Earwicker
17

Skenario yang saya lakukan menggunakan IDisposable: bersihkan sumber daya yang tidak dikelola, berhenti berlangganan untuk acara, tutup koneksi

Ungkapan yang saya gunakan untuk mengimplementasikan IDisposable ( bukan threadsafe ):

class MyClass : IDisposable {
    // ...

    #region IDisposable Members and Helpers
    private bool disposed = false;

    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing) {
        if (!this.disposed) {
            if (disposing) {
                // cleanup code goes here
            }
            disposed = true;
        }
    }

    ~MyClass() {
        Dispose(false);
    }
    #endregion
}
olli-MSFT
sumber
Penjelasan pola lengkap dapat ditemukan di msdn.microsoft.com/en-us/library/b1yfkh5e.aspx
LicenseQ
3
seharusnya tidak pernah menyertakan finalizer kecuali jika Anda memiliki sumber daya yang tidak dikelola. Bahkan kemudian, implementasi yang disukai adalah membungkus sumber daya yang tidak dikelola di SafeHandle.
Dave Black
11

Yap, kode itu benar-benar berlebihan dan tidak perlu dan itu tidak membuat pengumpul sampah melakukan apa pun yang tidak akan dilakukannya (sekali contoh MyCollection keluar dari ruang lingkup, yaitu.) Terutama .Clear()panggilan.

Jawab untuk hasil edit Anda: Semacam. Jika saya melakukan ini:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has no Dispose() method
    instance.FillItWithAMillionStrings();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Secara fungsional identik dengan ini untuk keperluan manajemen memori:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has your Dispose()
    instance.FillItWithAMillionStrings();
    instance.Dispose();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Jika Anda benar-benar benar-benar perlu membebaskan memori ini seketika, hubungi GC.Collect(). Tidak ada alasan untuk melakukan ini di sini. Memori akan dibebaskan saat dibutuhkan.

mqp
sumber
2
re: "Memori akan dibebaskan saat dibutuhkan." Alih-alih mengatakan, "ketika GC memutuskan itu diperlukan." Anda mungkin melihat masalah kinerja sistem sebelum GC memutuskan bahwa memori benar - benar diperlukan. Membebaskannya sekarang mungkin tidak penting, tetapi mungkin bermanfaat.
Jesse Chisholm
1
Ada beberapa kasus sudut di mana pembatalan referensi dalam koleksi dapat mempercepat pengumpulan sampah dari item yang dimaksud. Misalnya, jika array besar dibuat dan diisi dengan referensi ke item yang baru dibuat lebih kecil, tetapi tidak diperlukan untuk waktu yang lama setelah itu, meninggalkan array dapat menyebabkan barang-barang itu disimpan sampai Level 2 GC berikutnya, sementara memusatkan perhatian terlebih dahulu dapat membuat item memenuhi syarat untuk level 0 berikutnya atau level 1 GC. Yang pasti, memiliki objek berumur pendek yang besar di Large Object Heap toh adalah hal yang menjijikkan (saya tidak suka desainnya) tapi ...
supercat
1
... menghapus array seperti itu sebelum meninggalkannya, terkadang saya mengurangi dampak GC.
supercat
11

Jika MyCollectionakan menjadi sampah yang dikumpulkan, maka Anda tidak perlu membuangnya. Melakukan hal itu hanya akan mengocok CPU lebih dari yang diperlukan, dan bahkan dapat membatalkan beberapa analisis pra-perhitungan bahwa pemulung telah melakukan.

Saya menggunakan IDisposableuntuk melakukan hal-hal seperti memastikan utas dibuang dengan benar, bersama dengan sumber daya yang tidak dikelola.

EDIT Menanggapi komentar Scott:

Satu-satunya waktu metrik kinerja GC terpengaruh adalah ketika panggilan [sic] GC.Collect () dibuat "

Secara konseptual, GC mempertahankan pandangan dari grafik referensi objek, dan semua referensi darinya dari tumpukan frame dari thread. Tumpukan ini bisa sangat besar dan menjangkau banyak halaman memori. Sebagai optimasi, GC melakukan cache analisisnya pada halaman yang tidak mungkin berubah sangat sering untuk menghindari pemindaian ulang halaman yang tidak perlu. GC menerima pemberitahuan dari kernel ketika data dalam halaman berubah, sehingga ia tahu bahwa halaman tersebut kotor dan membutuhkan pemindaian ulang. Jika koleksi ada di Gen0 maka kemungkinan hal-hal lain di halaman juga berubah, tetapi ini kurang mungkin di Gen1 dan Gen2. Secara anekdot, kait ini tidak tersedia di Mac OS X untuk tim yang mem-porting GC ke Mac untuk mendapatkan plug-in Silverlight bekerja pada platform itu.

Poin lain terhadap pembuangan sumber daya yang tidak perlu: bayangkan situasi di mana suatu proses sedang dibongkar. Bayangkan juga bahwa proses telah berjalan selama beberapa waktu. Kemungkinannya adalah banyak halaman memori proses tersebut telah ditukar ke disk. Paling tidak mereka tidak lagi dalam cache L1 atau L2. Dalam situasi seperti itu tidak ada gunanya aplikasi yang membongkar untuk menukar semua data dan kode halaman kembali ke memori untuk 'melepaskan' sumber daya yang akan dirilis oleh sistem operasi ketika proses berakhir. Ini berlaku untuk sumber daya yang dikelola dan bahkan sumber daya tertentu yang tidak dikelola. Hanya sumber daya yang membuat utas non-latar belakang tetap hidup yang harus dibuang, jika tidak prosesnya akan tetap hidup.

Sekarang, selama eksekusi normal ada sumber daya sesaat yang harus dibersihkan dengan benar (seperti @fezmonkey menunjukkan koneksi database, soket, pegangan jendela ) untuk menghindari kebocoran memori yang tidak dikelola. Ini adalah hal-hal yang harus dibuang. Jika Anda membuat beberapa kelas yang memiliki utas (dan oleh yang saya maksud adalah ia menciptakannya dan karenanya bertanggung jawab untuk memastikannya berhenti, setidaknya dengan gaya pengkodean saya), maka kelas itu kemungkinan besar harus menerapkan IDisposabledan meruntuhkan utas selama Dispose.

Kerangka NET. Menggunakan IDisposableantarmuka sebagai sinyal, bahkan peringatan, untuk pengembang bahwa kelas ini harus dibuang. Saya tidak bisa memikirkan jenis apa pun dalam kerangka kerja yang menerapkan IDisposable(tidak termasuk implementasi antarmuka eksplisit) di mana pembuangan adalah opsional.

Drew Noakes
sumber
Memanggil Buang sangat sah, sah, dan dianjurkan. Objek yang menerapkan IDisposable biasanya melakukannya karena suatu alasan. Satu-satunya waktu metrik kinerja GC terpengaruh adalah ketika panggilan GC.Collect () dibuat.
Scott Dorman
Untuk banyak kelas .net, pembuangan adalah "agak" opsional, yang berarti bahwa meninggalkan instance "biasanya" tidak akan menimbulkan masalah selama orang tidak menjadi gila membuat instance baru dan meninggalkannya. Sebagai contoh, kode yang dihasilkan kompiler untuk kontrol tampaknya membuat font ketika kontrol dipakai dan meninggalkan mereka ketika formulir dibuang; jika seseorang membuat dan membuang ribuan kontrol, ini bisa mengikat ribuan pegangan GDI, tetapi dalam banyak kasus kontrol tidak dibuat dan dihancurkan sebanyak itu. Meskipun demikian, seseorang harus tetap berusaha menghindari pengabaian tersebut.
supercat
1
Dalam hal font, saya menduga masalahnya adalah bahwa Microsoft tidak pernah benar-benar mendefinisikan entitas apa yang bertanggung jawab untuk membuang objek "font" yang ditugaskan ke kontrol; dalam beberapa kasus, kontrol dapat berbagi font dengan objek yang berumur panjang, sehingga memiliki kontrol Buang font akan menjadi buruk. Dalam kasus lain, font akan ditugaskan ke kontrol dan tidak di tempat lain, jadi jika kontrol tidak membuangnya tidak ada yang mau. Kebetulan, kesulitan dengan font ini bisa dihindari seandainya ada kelas FontTemplate yang tidak dapat dibuang, karena kontrol tampaknya tidak menggunakan pegangan GDI untuk Font mereka.
supercat
Pada topik Dispose()panggilan opsional , lihat: stackoverflow.com/questions/913228/…
RJ Cuthbertson
7

Pada contoh yang Anda posting, masih tidak "membebaskan memori sekarang". Semua memori adalah sampah yang dikumpulkan, tetapi itu memungkinkan memori dikumpulkan pada generasi sebelumnya . Anda harus menjalankan beberapa tes untuk memastikan.


Pedoman Kerangka Desain adalah pedoman, dan bukan aturan. Mereka memberi tahu Anda apa antarmuka utamanya, kapan menggunakannya, bagaimana menggunakannya, dan kapan tidak menggunakannya.

Saya pernah membaca kode yang merupakan RollBack sederhana () tentang kegagalan menggunakan IDisposable. Kelas MiniTx di bawah ini akan memeriksa bendera pada Buang () dan jika Commitpanggilan tidak pernah terjadi maka akan memanggil Rollbackdirinya sendiri. Itu menambahkan lapisan tipuan membuat kode panggilan jauh lebih mudah untuk dipahami dan dipelihara. Hasilnya terlihat seperti:

using( MiniTx tx = new MiniTx() )
{
    // code that might not work.

    tx.Commit();
} 

Saya juga melihat timing / logging code melakukan hal yang sama. Dalam hal ini metode Buang () menghentikan timer dan mencatat bahwa blok telah keluar.

using( LogTimer log = new LogTimer("MyCategory", "Some message") )
{
    // code to time...
}

Jadi di sini adalah beberapa contoh nyata yang tidak melakukan pembersihan sumber daya yang tidak dikelola, tetapi berhasil menggunakan IDisposable untuk membuat kode bersih.

Robert Paulson
sumber
Lihatlah contoh @Daniel Earwicker menggunakan fungsi tingkat tinggi. Untuk pembandingan, pengaturan waktu, pencatatan, dll. Tampaknya jauh lebih mudah.
Aluan Haddad
6

Saya tidak akan mengulangi hal-hal yang biasa tentang Menggunakan atau membebaskan sumber daya yang tidak dikelola, yang semuanya telah dibahas. Tetapi saya ingin menunjukkan apa yang tampaknya merupakan kesalahpahaman umum.
Diberi kode berikut

Kelas Publik LargeStuff
  Mengimplementasikan IDisposable
  Pribadi _Large sebagai string ()

  'Beberapa kode aneh yang artinya _Large sekarang berisi beberapa juta string panjang.

  Public Sub Dispose () Menerapkan IDisposable.Dispose
    _Large = Tidak Ada
  End Sub

Saya menyadari bahwa penerapan Disposable tidak mengikuti pedoman saat ini, tetapi mudah-mudahan Anda semua mendapatkan idenya.
Sekarang, ketika Buang dipanggil, berapa banyak memori yang dibebaskan?

Jawab: Tidak ada.
Memanggil Buang dapat melepaskan sumber daya yang tidak dikelola, itu TIDAK BISA merebut kembali memori yang dikelola, hanya GC yang bisa melakukannya. Itu tidak berarti bahwa di atas bukan ide yang baik, mengikuti pola di atas sebenarnya masih ide yang baik. Setelah Buang dijalankan, tidak ada yang menghentikan GC mengklaim kembali memori yang sedang digunakan oleh _Large, meskipun instance LargeStuff mungkin masih dalam ruang lingkup. String di _Large mungkin juga ada di gen 0 tetapi instance LargeStuff mungkin gen 2, jadi sekali lagi, memori akan diklaim kembali lebih cepat.
Tidak ada gunanya menambahkan finaliser untuk memanggil metode Buang yang ditunjukkan di atas. Itu hanya akan Tunda klaim ulang memori untuk memungkinkan finaliser berjalan.

pipGeek
sumber
1
Jika turunan dari LargeStufftelah ada cukup lama untuk sampai ke Generasi 2, dan jika _Largememegang referensi ke string yang baru dibuat yang ada di Generasi 0, maka jika turunan LargeStuffditinggalkan tanpa nulling _Large, maka string yang dirujuk oleh _Largeakan disimpan sampai koleksi Gen2 berikutnya. Mengabaikan _Largedapat membiarkan string dihilangkan pada koleksi Gen0 berikutnya. Dalam kebanyakan kasus, membatalkan referensi tidak membantu, tetapi ada kasus di mana ia dapat menawarkan beberapa manfaat.
supercat
5

Terlepas dari penggunaan utamanya sebagai cara untuk mengontrol seumur hidup dari sumber daya sistem (benar-benar tertutup oleh jawaban mengagumkan dari Ian , pujian!), Yang IDisposable / menggunakan combo juga dapat digunakan untuk ruang lingkup perubahan keadaan sumber daya (kritis) global yang : yang konsol , yang benang , yang proses , setiap obyek global seperti contoh aplikasi .

Saya telah menulis artikel tentang pola ini: http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

Ini menggambarkan bagaimana Anda dapat melindungi beberapa negara global yang sering digunakan dengan cara yang dapat digunakan kembali dan dibaca : warna konsol , kultur utas saat ini , properti objek aplikasi Excel ...

Pragmateek
sumber
4

Jika ada, saya berharap kode menjadi kurang efisien daripada ketika meninggalkannya.

Memanggil metode Clear () tidak perlu, dan GC mungkin tidak akan melakukannya jika Buang tidak melakukannya ...

Arjan Einbu
sumber
2

Ada beberapa hal yang dilakukan Dispose()operasi dalam kode contoh yang mungkin memiliki efek yang tidak akan terjadi karena GC objek normal MyCollection.

Jika benda yang dirujuk oleh _theListatau _theDictdirujuk oleh benda lain, maka benda List<>atau Dictionary<>benda itu tidak akan dikoleksi tetapi tiba-tiba tidak memiliki konten. Jika tidak ada operasi Buang () seperti dalam contoh, koleksi tersebut akan tetap berisi kontennya.

Tentu saja, jika ini adalah situasi yang saya sebut itu desain yang rusak - Saya hanya menunjukkan (dengan rasa pedas, saya kira) bahwa Dispose()operasi mungkin tidak sepenuhnya berlebihan, tergantung pada apakah ada kegunaan lain dari List<>atau Dictionary<>yang tidak ditunjukkan dalam fragmen.

Michael Burr
sumber
Itu adalah bidang pribadi, jadi saya pikir adil untuk menganggap OP tidak memberikan referensi kepada mereka.
mqp
1) fragmen kode hanyalah kode contoh, jadi saya hanya menunjukkan bahwa mungkin ada efek samping yang mudah diabaikan; 2) bidang pribadi sering menjadi target properti pengambil / metode - mungkin terlalu banyak (pengambil / penentu dianggap oleh beberapa orang sebagai sedikit anti-pola).
Michael Burr
2

Satu masalah dengan sebagian besar diskusi tentang "sumber daya yang tidak dikelola" adalah bahwa mereka tidak benar-benar mendefinisikan istilah, tetapi tampaknya menyiratkan bahwa itu ada hubungannya dengan kode yang tidak dikelola. Meskipun benar bahwa banyak jenis sumber daya yang tidak dikelola melakukan antarmuka dengan kode yang tidak dikelola, memikirkan sumber daya yang tidak dikelola dalam istilah tersebut tidak membantu.

Alih-alih, seseorang harus mengakui kesamaan semua sumber daya yang dikelola: mereka semua mensyaratkan suatu objek yang meminta beberapa 'hal' di luar untuk melakukan sesuatu atas namanya, dengan merugikan beberapa 'hal' lain, dan entitas lain setuju untuk melakukannya sampai pemberitahuan selanjutnya. Jika objek itu akan ditinggalkan dan lenyap tanpa jejak, tidak ada yang akan mengatakan bahwa di luar 'hal' yang tidak perlu lagi mengubah perilakunya atas nama objek yang tidak lagi ada; akibatnya, kegunaan benda itu akan berkurang secara permanen.

Sumber daya yang tidak dikelola, kemudian, merepresentasikan suatu persetujuan oleh 'benda' luar untuk mengubah perilakunya atas nama suatu objek, yang akan sia-sia merusak kegunaan dari 'benda' luar itu jika objek tersebut ditinggalkan dan tidak ada lagi. Sumber daya yang dikelola adalah objek yang merupakan penerima manfaat dari perjanjian semacam itu, tetapi yang telah mendaftar untuk menerima pemberitahuan jika ditinggalkan, dan yang akan menggunakan pemberitahuan tersebut untuk membereskan urusannya sebelum dihancurkan.

supercat
sumber
Ya, IMO, definisi objek yang tidak dikelola jelas; objek non-GC .
Eonil
1
@Eonil: Objek Tidak dikelola! = Sumber Daya Tidak dikelola. Hal-hal seperti peristiwa dapat diimplementasikan sepenuhnya menggunakan objek yang dikelola, tetapi masih merupakan sumber daya yang tidak dikelola karena - setidaknya dalam kasus objek berumur pendek yang berlangganan acara objek berumur panjang - GC tidak tahu apa-apa tentang cara membersihkannya .
supercat
2

IDisposable baik untuk berhenti berlangganan dari acara.

Adam Speight
sumber
2

Definisi pertama. Bagi saya sumber daya yang tidak dikelola berarti beberapa kelas, yang mengimplementasikan antarmuka IDisposable atau sesuatu yang dibuat dengan penggunaan panggilan ke dll. GC tidak tahu bagaimana menangani objek seperti itu. Jika kelas hanya memiliki tipe nilai saja, maka saya tidak menganggap kelas ini sebagai kelas dengan sumber daya yang tidak dikelola. Untuk kode saya, saya mengikuti praktik selanjutnya:

  1. Jika dibuat oleh saya kelas menggunakan beberapa sumber daya yang tidak dikelola maka itu berarti bahwa saya juga harus mengimplementasikan antarmuka IDisposable untuk membersihkan memori.
  2. Bersihkan benda segera setelah saya selesai menggunakannya.
  3. Dalam metode buang saya, saya beralih ke semua anggota kelas IDisposable dan memanggil Buang.
  4. Dalam metode Buang, panggil GC.SuppressFinalize (ini) untuk memberi tahu pengumpul sampah bahwa objek saya sudah dibersihkan. Saya melakukannya karena memanggil GC adalah operasi yang mahal.
  5. Sebagai tindakan pencegahan tambahan, saya mencoba membuat kemungkinan panggilan Buang () beberapa kali.
  6. Kadang saya menambahkan anggota pribadi _disposed dan check-in metode panggilan apakah objek dibersihkan. Dan jika sudah dibersihkan maka hasilkan template ObjectDisposedException
    following menunjukkan apa yang saya jelaskan dalam kata-kata sebagai contoh kode:

public class SomeClass : IDisposable
    {
        /// <summary>
        /// As usually I don't care was object disposed or not
        /// </summary>
        public void SomeMethod()
        {
            if (_disposed)
                throw new ObjectDisposedException("SomeClass instance been disposed");
        }

        public void Dispose()
        {
            Dispose(true);
        }

        private bool _disposed;

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;
            if (disposing)//we are in the first call
            {
            }
            _disposed = true;
        }
    }
Yuriy Zaletskyy
sumber
1
"Bagi saya sumber daya yang tidak dikelola berarti beberapa kelas, yang mengimplementasikan antarmuka IDisposable atau sesuatu yang dibuat dengan penggunaan panggilan ke dll." Jadi Anda mengatakan bahwa jenis apa pun yang is IDisposableseharusnya dianggap sebagai sumber daya yang tidak dikelola? Tampaknya itu tidak benar. Juga jika tipe implementasi adalah tipe nilai murni Anda tampaknya menyarankan bahwa itu tidak perlu dibuang. Itu juga tampaknya salah.
Aluan Haddad
Semua orang menilai sendiri. Saya tidak suka menambahkan sesuatu ke kode saya hanya karena penambahan. Itu berarti jika saya menambahkan IDisposable, itu berarti saya telah membuat semacam fungsionalitas yang tidak dapat dikelola oleh GC atau saya kira itu tidak akan dapat mengaturnya seumur hidup dengan baik.
Yuriy Zaletskyy
2

Contoh kode yang Anda berikan bukan contoh yang baik untuk IDisposabledigunakan. Kliring kamus biasanya tidak masuk ke Disposemetode. Item kamus akan dihapus dan dibuang ketika keluar dari ruang lingkup.IDisposableimplementasi diperlukan untuk membebaskan beberapa memori / penangan yang tidak akan melepaskan / membebaskan bahkan setelah mereka keluar dari jangkauan.

Contoh berikut menunjukkan contoh yang baik untuk pola IDisposable dengan beberapa kode dan komentar.

public class DisposeExample
{
    // A base class that implements IDisposable. 
    // By implementing IDisposable, you are announcing that 
    // instances of this type allocate scarce resources. 
    public class MyResource: IDisposable
    {
        // Pointer to an external unmanaged resource. 
        private IntPtr handle;
        // Other managed resource this class uses. 
        private Component component = new Component();
        // Track whether Dispose has been called. 
        private bool disposed = false;

        // The class constructor. 
        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }

        // Implement IDisposable. 
        // Do not make this method virtual. 
        // A derived class should not be able to override this method. 
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method. 
            // Therefore, you should call GC.SupressFinalize to 
            // take this object off the finalization queue 
            // and prevent finalization code for this object 
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios. 
        // If disposing equals true, the method has been called directly 
        // or indirectly by a user's code. Managed and unmanaged resources 
        // can be disposed. 
        // If disposing equals false, the method has been called by the 
        // runtime from inside the finalizer and you should not reference 
        // other objects. Only unmanaged resources can be disposed. 
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called. 
            if(!this.disposed)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources. 
                if(disposing)
                {
                    // Dispose managed resources.
                    component.Dispose();
                }

                // Call the appropriate methods to clean up 
                // unmanaged resources here. 
                // If disposing is false, 
                // only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;

                // Note disposing has been done.
                disposed = true;

            }
        }

        // Use interop to call the method necessary 
        // to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

        // Use C# destructor syntax for finalization code. 
        // This destructor will run only if the Dispose method 
        // does not get called. 
        // It gives your base class the opportunity to finalize. 
        // Do not provide destructors in types derived from this class.
        ~MyResource()
        {
            // Do not re-create Dispose clean-up code here. 
            // Calling Dispose(false) is optimal in terms of 
            // readability and maintainability.
            Dispose(false);
        }
    }
    public static void Main()
    {
        // Insert code here to create 
        // and use the MyResource object.
    }
}
CharithJ
sumber
1

Kasus penggunaan yang paling dapat dibenarkan untuk pembuangan sumber daya yang dikelola, adalah persiapan bagi GC untuk merebut kembali sumber daya yang sebelumnya tidak akan pernah dikumpulkan.

Contoh utama adalah referensi melingkar.

Meskipun praktik terbaik untuk menggunakan pola yang menghindari referensi melingkar, jika Anda berakhir dengan (misalnya) objek 'anak' yang memiliki referensi kembali ke 'induknya', ini dapat menghentikan koleksi GC dari induk jika Anda hanya meninggalkan referensi dan mengandalkan GC - plus jika Anda telah mengimplementasikan finalizer, itu tidak akan pernah dipanggil.

Satu-satunya cara untuk menyelesaikan ini adalah dengan mematahkan referensi melingkar secara manual dengan mengatur referensi Orang Tua ke nol pada anak-anak.

Menerapkan IDisposable pada orang tua dan anak-anak adalah cara terbaik untuk melakukan ini. Ketika Buang dipanggil pada Orang Tua, panggil Buang semua Anak, dan dalam metode Buang anak, atur referensi Orang Tua ke nol.

kotak kontrol
sumber
4
Untuk sebagian besar, GC tidak bekerja dengan mengidentifikasi benda mati, melainkan dengan mengidentifikasi benda hidup. Setelah setiap siklus gc, untuk setiap objek yang telah didaftarkan untuk finalisasi, disimpan pada tumpukan objek besar, atau merupakan target live WeakReference, sistem akan memeriksa bendera yang menunjukkan bahwa referensi rooted hidup ditemukan dalam siklus GC terakhir , dan akan menambahkan objek ke antrian objek yang membutuhkan finalisasi segera, melepaskan objek dari tumpukan objek besar, atau membatalkan referensi lemah. Referensi sirkular tidak akan membuat benda hidup jika tidak ada referensi lainnya.
supercat
1

Saya melihat banyak jawaban telah bergeser untuk berbicara tentang penggunaan IDisposable untuk sumber daya yang dikelola dan tidak dikelola. Saya menyarankan artikel ini sebagai salah satu penjelasan terbaik yang saya temukan untuk bagaimana IDisposable seharusnya digunakan.

https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About

Untuk pertanyaan aktual; Jika Anda menggunakan IDisposable untuk membersihkan objek yang dikelola yang menggunakan banyak memori, jawaban singkatnya adalah tidak . Alasannya adalah bahwa sekali Anda Buang IDisposable Anda harus membiarkannya keluar dari ruang lingkup. Pada titik itu, setiap objek anak yang dirujuk juga di luar jangkauan dan akan dikumpulkan.

Satu-satunya pengecualian nyata untuk ini adalah jika Anda memiliki banyak memori yang diikat dalam objek yang dikelola dan Anda telah memblokir utas itu menunggu beberapa operasi selesai. Jika objek-objek itu di mana tidak akan diperlukan setelah panggilan itu selesai maka pengaturan referensi tersebut ke nol mungkin memungkinkan pengumpul sampah untuk mengumpulkan mereka lebih cepat. Tapi skenario itu akan merepresentasikan kode buruk yang perlu di refactored - bukan use case IDisposable.

MikeJ
sumber
1
Saya tidak mengerti mengapa seseorang meletakkan -1 pada jawaban Anda
Sebastian Oscar Lopez