Penggunaan metode Finalisasi / Buang dalam C #

381

C # 2008

Saya telah mengerjakan ini untuk sementara waktu sekarang, dan saya masih bingung tentang penggunaan metode finalisasi dan buang dalam kode. Pertanyaan saya di bawah ini:

  1. Saya tahu bahwa kita hanya perlu penyelesai sementara membuang sumber daya yang tidak dikelola. Namun, jika ada sumber daya yang dikelola yang membuat panggilan ke sumber daya yang tidak dikelola, apakah masih perlu menerapkan finalizer?

  2. Namun, jika saya mengembangkan kelas yang tidak menggunakan sumber daya yang tidak dikelola - langsung atau tidak langsung, haruskah saya mengimplementasikannya IDisposable agar klien dari kelas itu dapat menggunakan 'pernyataan penggunaan'?

    Apakah layak menerapkan IDisposable hanya untuk memungkinkan klien kelas Anda menggunakan pernyataan menggunakan?

    using(myClass objClass = new myClass())
    {
        // Do stuff here
    }
  3. Saya telah mengembangkan kode sederhana di bawah ini untuk menunjukkan penyelesaian / buang penggunaan:

    public class NoGateway : IDisposable
    {
        private WebClient wc = null;
    
        public NoGateway()
        {
            wc = new WebClient();
            wc.DownloadStringCompleted += wc_DownloadStringCompleted;
        }
    
    
        // Start the Async call to find if NoGateway is true or false
        public void NoGatewayStatus()
        {
            // Start the Async's download
                // Do other work here
            wc.DownloadStringAsync(new Uri(www.xxxx.xxx));
        }
    
        private void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            // Do work here
        }
    
        // Dispose of the NoGateway object
        public void Dispose()
        {
            wc.DownloadStringCompleted -= wc_DownloadStringCompleted;
            wc.Dispose();
            GC.SuppressFinalize(this);
        }
    }

Pertanyaan tentang kode sumber:

  1. Di sini saya belum menambahkan finalizer, dan biasanya finalizer akan dipanggil oleh GC, dan finalizer akan memanggil Buang. Karena saya tidak memiliki finalizer, kapan saya memanggil metode Buang? Apakah klien dari kelas yang harus memanggilnya?

    Jadi kelas saya dalam contoh disebut NoGateway dan klien dapat menggunakan dan membuang kelas seperti ini:

    using(NoGateway objNoGateway = new NoGateway())
    {
        // Do stuff here   
    }

    Apakah metode Buang dipanggil secara otomatis ketika eksekusi mencapai akhir blok penggunaan, atau apakah klien harus memanggil metode buang secara manual? yaitu

    NoGateway objNoGateway = new NoGateway();
    // Do stuff with object
    objNoGateway.Dispose(); // finished with it
  2. Saya menggunakan WebClientkelas di NoGatewaykelas saya . Karena WebClientmengimplementasikan IDisposableantarmuka, apakah ini berarti WebClientsecara tidak langsung menggunakan sumber daya yang tidak dikelola? Apakah ada aturan yang keras dan cepat untuk mengikuti ini? Bagaimana saya tahu bahwa suatu kelas menggunakan sumber daya yang tidak dikelola?

ant2009
sumber
1
Apakah pola desain yang rumit ini sebenarnya diperlukan untuk menyelesaikan masalah relase sumber daya ini?
zinking

Jawaban:

422

Pola IDisposable yang direkomendasikan ada di sini . Saat memprogram kelas yang menggunakan IDisposable, umumnya Anda harus menggunakan dua pola:

Saat menerapkan kelas tertutup yang tidak menggunakan sumber daya yang tidak dikelola, Anda cukup menerapkan metode Buang seperti dengan implementasi antarmuka normal:

public sealed class A : IDisposable
{
    public void Dispose()
    {
        // get rid of managed resources, call Dispose on member variables...
    }
}

Saat menerapkan kelas yang tidak disegel, lakukan seperti ini:

public class B : IDisposable
{    
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // get rid of managed resources
        }   
        // get rid of unmanaged resources
    }

    // only if you use unmanaged resources directly in B
    //~B()
    //{
    //    Dispose(false);
    //}
}

Perhatikan bahwa saya belum menyatakan finalizer di B; Anda hanya harus menerapkan finalizer jika Anda memiliki sumber daya yang tidak terkelola untuk dibuang. CLR berurusan dengan objek yang dapat diselesaikan secara berbeda dengan objek yang tidak dapat diselesaikan, meskipun SuppressFinalizedisebut.

Jadi, Anda tidak boleh mendeklarasikan finalizer kecuali Anda harus melakukannya, tetapi Anda memberikan hook kelas kepada Anda untuk menelepon Anda Disposedan mengimplementasikan finalizer sendiri jika mereka menggunakan sumber daya yang tidak dikelola secara langsung:

public class C : B
{
    private IntPtr m_Handle;

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            // get rid of managed resources
        }
        ReleaseHandle(m_Handle);

        base.Dispose(disposing);
    }

    ~C() {
        Dispose(false);
    }
}

Jika Anda tidak menggunakan sumber daya yang tidak dikelola secara langsung ( SafeHandledan teman-teman tidak masuk hitungan, karena mereka menyatakan finalizer mereka sendiri), maka jangan mengimplementasikan finalizer, karena GC berurusan dengan kelas finalizable secara berbeda, bahkan jika Anda kemudian menekan finalizer. Perhatikan juga, meskipun Btidak memiliki finalizer, ia tetap memanggilSuppressFinalize untuk berurusan dengan benar dengan setiap subclass yang mengimplementasikan finalizer.

Ketika sebuah kelas mengimplementasikan antarmuka IDisposable, itu berarti bahwa di suatu tempat ada beberapa sumber daya yang tidak dikelola yang harus dihilangkan ketika Anda selesai menggunakan kelas. Sumber daya aktual dirangkum dalam kelas; Anda tidak perlu menghapusnya secara eksplisit. Cukup dengan menelepon Dispose()atau membungkus kelas dalam a using(...) {}akan memastikan sumber daya yang tidak dikelola dihilangkan seperlunya.

thecoop
sumber
26
Saya setuju dengan thecoop. Perhatikan bahwa Anda tidak memerlukan finalizer jika Anda hanya berurusan dengan sumber daya yang dikelola (pada kenyataannya, Anda TIDAK harus mencoba mengakses objek yang dikelola dari dalam finalizer Anda (selain "ini"), karena tidak ada urutan jaminan di mana GC akan membersihkan objek. Juga, jika Anda menggunakan .Net 2.0 atau lebih baik, Anda dapat (dan seharusnya) menggunakan SafeHandles untuk membungkus gagang yang tidak dikelola. Safehandles sangat mengurangi kebutuhan Anda untuk menulis finalizer untuk kelas terkelola Anda sama sekali. Blogs.msdn. com / bclteam / arsip / 2005/03/16 / 396900.aspx
JMarsch
5
Saya pikir lebih baik melakukan panggilan ke MessageBox.Show ("Kesalahan," + GetType (). Nama + "tidak dibuang") di finalizer, karena objek sekali pakai harus SELALU dibuang, dan jika Anda gagal melakukan ini sebaiknya waspada dengan fakta sedini mungkin.
erikkallen
95
@ erikkallen apakah itu lelucon? :)
Alex Norcliffe
2
sebagai upaya komputasi ekstra diperlukan dalam CLR untuk melacak kelas dengan finalizer aktif. - Menerapkan finalizer menyebabkan hal ini terjadi. Memanggil GC.SuppressFinalize berarti bahwa Finalizer tidak boleh dipanggil oleh runtime. Itu tetap berjalan Gen2 terlepas. Jangan tambahkan finalizer jika Anda tidak berurusan dengan sumber daya yang dikelola. Pengubah kelas yang disegel atau tidak tersegel tidak relevan untuk titik itu.
Ritch Melton
3
@Ritch: kutipan? Itu tidak selalu merupakan hal yang buruk; jika Anda menerapkan IDisposable, kemungkinan itu akan bertahan sebentar. Anda menyimpan CLR dengan upaya menyalinnya dari Gen0 -> Gen1 -> Gen2
thecoop
123

Pola resmi untuk mengimplementasikannya IDisposablesulit dipahami. Saya percaya ini lebih baik :

public class BetterDisposableClass : IDisposable {

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

  protected virtual void CleanUpManagedResources() { 
    // ...
  }
  protected virtual void CleanUpNativeResources() {
    // ...
  }

  ~BetterDisposableClass() {
    CleanUpNativeResources();
  }

}

Sebuah lebih baik solusi adalah memiliki aturan bahwa Anda selalu harus membuat kelas wrapper untuk setiap sumber daya unmanaged yang Anda butuhkan untuk menangani:

public class NativeDisposable : IDisposable {

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

  protected virtual void CleanUpNativeResource() {
    // ...
  }

  ~NativeDisposable() {
    CleanUpNativeResource();
  }

}

Dengan SafeHandledan turunannya, kelas-kelas ini harus sangat jarang .

Hasil untuk kelas sekali pakai yang tidak berurusan langsung dengan sumber daya yang tidak dikelola, bahkan di hadapan warisan, sangat kuat: mereka tidak perlu lagi peduli dengan sumber daya yang tidak dikelola lagi . Mereka akan mudah diimplementasikan dan dipahami:

public class ManagedDisposable : IDisposable {

  public virtual void Dispose() {
    // dispose of managed resources
  }

}
Jordão
sumber
@Kyle: Terima kasih! Saya juga suka :-) Ada tindak lanjut di sini .
Jordão
4
Meskipun satu hal yang ingin saya perhatikan adalah tidak mencegah dipanggil kedua kalinya.
HuseyinUslu
5
@HuseyinUslu: ini hanya inti dari polanya. Anda tentu dapat menambahkan disposedbendera dan memeriksa yang sesuai.
Jordão
2
@didibus: ini masalah sederhana menambahkan disposedbendera, periksa sebelum membuang dan atur setelah membuang. Lihat di sini untuk idenya. Anda juga harus memeriksa tanda sebelum metode apa pun di kelas. Masuk akal? Apakah ini rumit?
Jordão
1
+1 untuk "Solusi yang lebih baik lagi adalah memiliki aturan bahwa Anda selalu harus membuat kelas pembungkus untuk sumber daya yang tidak dikelola yang perlu Anda tangani" . Saya menemukan ini di addon untuk VLC dan saya telah menggunakannya sejak itu. Menghemat begitu banyak sakit kepala ...
Franz B.
37

Perhatikan bahwa implementasi IDisposable harus mengikuti pola di bawah ini (IMHO). Saya mengembangkan pola ini berdasarkan info dari beberapa .NET "allah" yang bagus .NET Framework Design Guidelines (perhatikan bahwa MSDN tidak mengikuti ini karena alasan tertentu!). Pedoman .NET Framework Design ditulis oleh Krzysztof Cwalina (CLR Architect pada saat itu) dan Brad Abrams (saya percaya Manajer Program CLR pada saat itu) dan Bill Wagner ([C Efektif] dan [Lebih Efektif C #] (cukup ambil lihat ini di Amazon.com:

Perhatikan bahwa Anda tidak akan pernah mengimplementasikan Finalizer kecuali kelas Anda secara langsung berisi (bukan warisan) sumber daya yang tidak dikelola. Setelah Anda menerapkan Finalizer di kelas, bahkan jika itu tidak pernah dipanggil, dijamin akan hidup untuk koleksi tambahan. Itu secara otomatis ditempatkan pada Antrian Finalisasi (yang berjalan pada satu utas). Juga, satu catatan yang sangat penting ... semua kode yang dieksekusi dalam Finalizer (harus Anda perlu menerapkannya) HARUS aman dari thread DAN pengecualian aman! Hal-hal buruk akan terjadi sebaliknya ... (yaitu perilaku yang tidak ditentukan dan dalam kasus pengecualian, aplikasi crash fatal yang tidak dapat dipulihkan).

Pola yang saya kumpulkan (dan menulis cuplikan kode) berikut:

#region IDisposable implementation

//TODO remember to make this class inherit from IDisposable -> $className$ : IDisposable

// Default initialization for a bool is 'false'
private bool IsDisposed { get; set; }

/// <summary>
/// Implementation of Dispose according to .NET Framework Design Guidelines.
/// </summary>
/// <remarks>Do not make this method virtual.
/// A derived class should not be able to override this method.
/// </remarks>
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.

    // Always use SuppressFinalize() in case a subclass
    // of this type implements a finalizer.
    GC.SuppressFinalize( this );
}

/// <summary>
/// Overloaded Implementation of Dispose.
/// </summary>
/// <param name="isDisposing"></param>
/// <remarks>
/// <para><list type="bulleted">Dispose(bool isDisposing) executes in two distinct scenarios.
/// <item>If <paramref name="isDisposing"/> equals true, the method has been called directly
/// or indirectly by a user's code. Managed and unmanaged resources
/// can be disposed.</item>
/// <item>If <paramref name="isDisposing"/> 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.</item></list></para>
/// </remarks>
protected virtual void Dispose( bool isDisposing )
{
    // TODO If you need thread safety, use a lock around these 
    // operations, as well as in your methods that use the resource.
    try
    {
        if( !this.IsDisposed )
        {
            if( isDisposing )
            {
                // TODO Release all managed resources here

                $end$
            }

            // TODO Release all unmanaged resources here



            // TODO explicitly set root references to null to expressly tell the GarbageCollector
            // that the resources have been disposed of and its ok to release the memory allocated for them.


        }
    }
    finally
    {
        // explicitly call the base class Dispose implementation
        base.Dispose( isDisposing );

        this.IsDisposed = true;
    }
}

//TODO Uncomment this code if this class will contain members which are UNmanaged
// 
///// <summary>Finalizer for $className$</summary>
///// <remarks>This finalizer will run only if the Dispose method does not get called.
///// It gives your base class the opportunity to finalize.
///// DO NOT provide finalizers in types derived from this class.
///// All code executed within a Finalizer MUST be thread-safe!</remarks>
//  ~$className$()
//  {
//     Dispose( false );
//  }
#endregion IDisposable implementation

Berikut adalah kode untuk mengimplementasikan IDisposable di kelas turunan. Perhatikan bahwa Anda tidak perlu membuat daftar warisan secara eksplisit dari IDisposable dalam definisi kelas turunan.

public DerivedClass : BaseClass, IDisposable (remove the IDisposable because it is inherited from BaseClass)


protected override void Dispose( bool isDisposing )
{
    try
    {
        if ( !this.IsDisposed )
        {
            if ( isDisposing )
            {
                // Release all managed resources here

            }
        }
    }
    finally
    {
        // explicitly call the base class Dispose implementation
        base.Dispose( isDisposing );
    }
}

Saya telah memposting implementasi ini di blog saya di: Cara Menerapkan Pola Buang dengan Benar

Dave Black
sumber
Dapatkah seseorang juga menambahkan pola untuk kelas turunan (berasal dari kelas dasar ini)
akjoshi
3
@akjoshi - Saya telah memperbarui pola di atas untuk memasukkan kode untuk kelas sekali pakai turunan. Juga perhatikan, JANGAN PERNAH mengimplementasikan Finalizer di kelas turunan ...
Dave Black
3
Microsoft sepertinya suka mengatur bendera "dibuang" di akhir metode yang dibuang, tetapi itu tampaknya salah bagi saya. Panggilan berlebihan untuk "Buang" seharusnya tidak melakukan apa-apa; sementara orang biasanya tidak berharap Buang dipanggil secara rekursif, hal-hal seperti itu bisa terjadi jika seseorang mencoba Buang benda yang ditinggalkan dalam keadaan tidak valid dengan pengecualian yang terjadi selama konstruksi atau operasi lainnya. Saya akan berpikir menggunakan bendera Interlocked.Exchangeinteger IsDisposeddi fungsi pembungkus non-virtual akan lebih aman.
supercat
@ DaveBlack: Bagaimana jika kelas dasar Anda tidak menggunakan sumber daya yang tidak dikelola, tetapi kelas turunan Anda tidak? Apakah Anda harus mengimplementasikan Finalizer di kelas turunan? Dan jika demikian, bagaimana Anda tahu bahwa kelas dasar belum mengimplementasikannya jika Anda tidak memiliki akses ke sumbernya?
Didier A.
@ DaveBlack "Saya mengembangkan pola ini berdasarkan info dari beberapa." "Dewa" NET yang luar biasa. "Jika salah satu dewa adalah Jon Skeet maka saya akan mengikuti saran Anda.
Elisabeth
23

Saya setuju dengan PM100 (dan seharusnya secara eksplisit mengatakan ini di posting saya sebelumnya).

Anda tidak boleh menerapkan IDisposable di kelas kecuali Anda membutuhkannya. Untuk lebih spesifik, ada sekitar 5 kali ketika Anda membutuhkan / harus menerapkan IDisposable:

  1. Kelas Anda secara eksplisit berisi (yaitu bukan melalui warisan) sumber daya yang dikelola yang menerapkan IDisposable dan harus dibersihkan setelah kelas Anda tidak lagi digunakan. Misalnya, jika kelas Anda berisi turunan dari Stream, DbCommand, DataTable, dll.

  2. Kelas Anda secara eksplisit berisi sumber daya yang dikelola yang menerapkan metode Tutup () - misalnya IDataReader, IDbConnection, dll. Perhatikan bahwa beberapa kelas ini menerapkan IDisposable dengan memiliki metode Buang () serta Tutup ().

  3. Kelas Anda secara eksplisit berisi sumber daya yang tidak dikelola - misalnya objek COM, pointer (ya, Anda dapat menggunakan pointer dalam C # yang dikelola tetapi mereka harus dideklarasikan dalam blok 'tidak aman', dll. Dalam kasus sumber daya yang tidak dikelola, Anda juga harus memastikan untuk sebut System.Runtime.InteropServices.Marshal.ReleaseComObject () pada RCW. Meskipun RCW, secara teori, merupakan pembungkus yang dikelola, masih ada penghitungan referensi yang terjadi di bawah selimut.

  4. Jika kelas Anda berlangganan acara menggunakan referensi kuat. Anda harus membatalkan registrasi / melepaskan diri dari peristiwa tersebut. Selalu pastikan ini bukan nol dulu sebelum mencoba membatalkan pendaftaran / lepaskan mereka !.

  5. Kelas Anda mengandung kombinasi apa pun di atas ...

Alternatif yang disarankan untuk bekerja dengan objek COM dan harus menggunakan Marshal.ReleaseComObject () adalah dengan menggunakan kelas System.Runtime.InteropServices.SafeHandle.

BCL (Tim Perpustakaan Kelas Dasar) memiliki posting blog yang bagus tentang hal ini di sini http://blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx

Satu catatan yang sangat penting untuk dibuat adalah bahwa jika Anda bekerja dengan WCF dan membersihkan sumber daya, Anda harus HAMPIR SELALU menghindari blok 'menggunakan'. Ada banyak posting blog di luar sana dan beberapa di MSDN tentang mengapa ini adalah ide yang buruk. Saya juga mempostingnya di sini - Jangan gunakan 'using ()' dengan proxy WCF

Dave Black
sumber
3
Saya percaya ada kasus ke-5: Jika kelas Anda berlangganan acara menggunakan referensi yang kuat, maka Anda harus menerapkan IDisposable dan membatalkan pendaftaran diri Anda dari peristiwa dalam metode Buang.
Didier A.
Hai, didibus. Ya kamu benar. Saya lupa tentang itu. Saya telah memodifikasi jawaban saya untuk memasukkannya sebagai kasing. Terima kasih.
Dave Black
Dokumentasi MSDN untuk pola buang menambahkan kasus lain: "PERTIMBANGKAN menerapkan Pola Buang Dasar pada kelas yang mereka sendiri tidak memegang sumber daya yang tidak dikelola atau benda sekali pakai tetapi cenderung memiliki subtipe yang berfungsi. Contoh yang bagus untuk hal ini adalah System.IO Kelas .Stream. Meskipun ini adalah kelas dasar abstrak yang tidak memiliki sumber daya apa pun, sebagian besar subkelasnya melakukannya dan karena ini, ia menerapkan pola ini. "
Gonen I
12

Menggunakan lambdas bukan IDisposable.

Saya tidak pernah senang dengan keseluruhan menggunakan / ide IDisposable. Masalahnya adalah itu mengharuskan penelepon untuk:

  • tahu bahwa mereka harus menggunakan IDisposable
  • ingat untuk menggunakan 'menggunakan'.

Metode pilihan baru saya adalah menggunakan metode pabrik dan lambda sebagai gantinya

Bayangkan saya ingin melakukan sesuatu dengan SqlConnection (sesuatu yang harus dibungkus menggunakan). Klasik yang akan Anda lakukan

using (Var conn = Factory.MakeConnection())
{
     conn.Query(....);
}

Jalan baru

Factory.DoWithConnection((conn)=>
{
    conn.Query(...);
}

Dalam kasus pertama penelepon tidak bisa menggunakan sintaks menggunakan. Dalam kasus kedua, pengguna tidak memiliki pilihan. Tidak ada metode yang membuat objek SqlConnection, pemanggil harus memanggil DoWithConnection.

DoWithConnection terlihat seperti ini

void DoWithConnection(Action<SqlConnection> action)
{
   using (var conn = MakeConnection())
   {
       action(conn);
   }
}

MakeConnection sekarang pribadi

pm100
sumber
2
Membungkus barang dalam lambda bisa menjadi pendekatan yang baik, tetapi memiliki batasan. Ini tidak terlalu buruk untuk situasi di mana, pada kenyataannya, semua konsumen kelas akan menggunakan blok "menggunakan", tetapi itu akan melarang situasi di mana metode akan menyimpan IDisposable di bidang kelas (baik secara langsung, atau dalam sesuatu seperti iterator ).
supercat
@supercat Anda dapat berargumen bahwa melarang penyimpanan hal-hal yang memonopoli sumber daya adalah hal yang baik. Model pinjaman yang saya usulkan di sini memaksa Anda untuk bersandar pada penggunaan sumber daya
pm100
Ini bisa menjadi hal yang baik, tetapi juga bisa membuat beberapa operasi yang sangat masuk akal menjadi sangat sulit. Misalnya, anggap bahwa tipe pembaca basis data, alih-alih menerapkan IEnumerable <T>, memaparkan metode DoForAll(Action<T>) where T:IComparable<T>, memanggil delegasi yang ditunjukkan pada setiap catatan. Diberikan dua objek seperti itu, yang keduanya akan mengembalikan data dalam urutan, bagaimana satu output semua item yang ada dalam satu koleksi tetapi tidak yang lain? Jika tipe diimplementasikan IEnumerable<T>, seseorang dapat melakukan operasi penggabungan, tetapi itu tidak akan berhasil DoForAll.
supercat
Satu-satunya cara saya dapat menggabungkan dua DoForAllkoleksi tanpa harus menyalin satu, secara keseluruhan, ke dalam beberapa struktur lain, adalah dengan menggunakan dua utas, yang akan menjadi lebih hoggish sumber daya daripada hanya menggunakan pasangan IEnumerable dan berhati-hati untuk membebaskan mereka.
supercat
-1: jawaban bagus untuk pertanyaan yang tidak ditanyakan. Ini akan menjadi jawaban yang bagus untuk "bagaimana cara membuat konsumsi objek IDisposable lebih mudah"
John Saunders
10

tidak ada yang menjawab pertanyaan tentang apakah Anda harus menerapkan IDisposable meskipun Anda tidak membutuhkannya.

Jawaban singkat: Tidak

Jawaban panjang:

Ini akan memungkinkan konsumen kelas Anda untuk menggunakan 'menggunakan'. Pertanyaan yang akan saya tanyakan adalah - mengapa mereka melakukannya? Sebagian besar devs tidak akan menggunakan 'menggunakan' kecuali mereka tahu bahwa mereka harus - dan bagaimana mereka tahu. Antara

  • itu obviuos mereka dari pengalaman (kelas soket misalnya)
  • itu didokumentasikan
  • mereka berhati-hati dan dapat melihat bahwa kelas mengimplementasikan IDisposable

Jadi dengan mengimplementasikan IDisposable Anda memberitahu devs (setidaknya beberapa) bahwa kelas ini merangkum sesuatu yang harus dirilis. Mereka akan menggunakan 'menggunakan' - tetapi ada kasus-kasus lain di mana penggunaan tidak memungkinkan (ruang lingkup objek tidak lokal); dan mereka harus mulai khawatir tentang masa hidup benda-benda dalam kasus-kasus lain - saya pasti khawatir. Tetapi ini tidak perlu

Anda menerapkan Idisposable untuk memungkinkan mereka menggunakan menggunakan, tetapi mereka tidak akan menggunakan menggunakan kecuali jika Anda memberi tahu mereka.

Jadi jangan lakukan itu

pm100
sumber
1
Saya tidak mengerti mengapa seorang dev tidak akan menggunakan menggunakan / membuang pada objek yang mengimplementasikan IDisposable (kecuali jika program akan keluar juga).
adrianm
1
intinya adalah seorang dev harus menulis semua panggilan untuk membuang semua jalur kode yang mengakibatkan unreferencing itu. SO misalnya jika saya meletakkan instance dalam Kamus, ketika saya menghapus entri dari kamus saya harus memanggil buang. Ada banyak kerumitan yang tidak diperlukan dalam kasus ini - objek tidak perlu dibuang
pm100
3
@ pm100 Re: Perlu menerapkan IDisposable - Ada artikel rinci di codeproject.com/KB/dotnet/idisposable.aspx yang membahas beberapa kasus langka ketika Anda mungkin ingin memikirkan hal ini (sangat jarang, saya yakin). Singkatnya: Jika Anda dapat memperkirakan kebutuhan IDisposable di masa depan, atau dalam objek turunan, maka Anda mungkin berpikir tentang mengimplementasikan IDisposable sebagai "no-op" di kelas dasar Anda untuk menghindari masalah "mengiris" di mana beberapa objek turunan membutuhkan pembuangan dan yang lainnya tidak.
Kevin P. Rice
4
  1. Jika Anda menggunakan objek terkelola lainnya yang menggunakan sumber daya yang tidak dikelola, itu bukan tanggung jawab Anda untuk memastikan mereka selesai. Tanggung jawab Anda adalah memanggil Buanglah benda-benda itu saat Buang dipanggil pada benda Anda, dan benda itu berhenti di sana.

  2. Jika kelas Anda tidak menggunakan sumber daya yang langka, saya gagal melihat mengapa Anda membuat kelas Anda mengimplementasikan IDisposable. Anda hanya harus melakukannya jika Anda:

    • Ketahuilah bahwa Anda akan memiliki sumber daya yang langka di objek Anda segera, tidak sekarang (dan maksud saya bahwa seperti "kita masih berkembang, itu akan ada di sini sebelum kita selesai", bukan seperti dalam "Saya pikir kita akan membutuhkan ini ")
    • Menggunakan sumber daya yang langka
  3. Ya, kode yang menggunakan kode Anda harus memanggil metode Buang objek Anda. Dan ya, kode yang menggunakan objek Anda dapat digunakan usingseperti yang Anda tunjukkan.

  4. (2 lagi?) Sepertinya WebClient menggunakan sumber daya yang tidak dikelola, atau sumber daya yang dikelola lainnya yang menerapkan IDisposable. Namun, alasan pastinya tidak penting. Yang penting adalah mengimplementasikan IDisposable, sehingga Anda harus bertindak berdasarkan pengetahuan itu dengan membuang objek ketika Anda selesai menggunakannya, bahkan jika ternyata WebClient tidak menggunakan sumber daya lain sama sekali.

Lasse V. Karlsen
sumber
4

Buang pola:

public abstract class DisposableObject : IDisposable
{
    public bool Disposed { get; private set;}      

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

    ~DisposableObject()
    {
        Dispose(false);
    }

    private void Dispose(bool disposing)
    {
        if (!Disposed)
        {
            if (disposing)
            {
                DisposeManagedResources();
            }

            DisposeUnmanagedResources();
            Disposed = true;
        }
    }

    protected virtual void DisposeManagedResources() { }
    protected virtual void DisposeUnmanagedResources() { }
}

Contoh warisan:

public class A : DisposableObject
{
    public Component components_a { get; set; }
    private IntPtr handle_a;

    protected override void DisposeManagedResources()
    {
        try
        {
          Console.WriteLine("A_DisposeManagedResources");
          components_a.Dispose();
          components_a = null;
        }
        finally
        { 
          base.DisposeManagedResources();
        }
    }

    protected override void DisposeUnmanagedResources()
    {
        try
        {
          Console.WriteLine("A_DisposeUnmanagedResources");
          CloseHandle(handle_a);
          handle_a = IntPtr.Zero;
        }
        finally
        { 
          base.DisposeUnmanagedResources();
        }
    }
}

public class B : A
{
    public Component components_b { get; set; }
    private IntPtr handle_b;

    protected override void DisposeManagedResources()
    {
        try
        {
          Console.WriteLine("B_DisposeManagedResources");
          components_b.Dispose();
          components_b = null;
        }
        finally
        { 
          base.DisposeManagedResources();
        }
    }

    protected override void DisposeUnmanagedResources()
    {
        try
        {
          Console.WriteLine("B_DisposeUnmanagedResources");
          CloseHandle(handle_b);
          handle_b = IntPtr.Zero;
        }
        finally
        { 
          base.DisposeUnmanagedResources();
        }
    }
}
Andrei Krasutski
sumber
4

Beberapa aspek dari jawaban lain sedikit salah karena 2 alasan:

Pertama,

using(NoGateway objNoGateway = new NoGateway())

sebenarnya setara dengan:

try
{
    NoGateway = new NoGateway();
}
finally
{
    if(NoGateway != null)
    {
        NoGateway.Dispose();
    }
}

Ini mungkin terdengar konyol karena operator 'baru' tidak boleh mengembalikan 'null' kecuali Anda memiliki pengecualian OutOfMemory. Tetapi pertimbangkan kasus-kasus berikut: 1. Anda memanggil FactoryClass yang mengembalikan sumber daya IDisposable atau 2. Jika Anda memiliki tipe yang mungkin atau mungkin tidak mewarisi dari IDisposable tergantung pada implementasinya - ingat bahwa saya telah melihat pola IDisposable diimplementasikan secara salah banyak kali di banyak klien di mana pengembang hanya menambahkan metode Buang () tanpa mewarisi dari IDisposable (buruk, buruk, buruk). Anda juga dapat memiliki kasus sumber daya IDisposable yang dikembalikan dari properti atau metode (lagi buruk, buruk, buruk - jangan 'memberikan sumber daya IDisposable Anda)

using(IDisposable objNoGateway = new NoGateway() as IDisposable)
{
    if (NoGateway != null)
    {
        ...

Jika operator 'as' mengembalikan null (atau properti atau metode yang mengembalikan sumber daya), dan kode Anda di blok 'using' melindungi terhadap 'null', kode Anda tidak akan meledak ketika mencoba memanggil Buang di objek nol karena cek nol 'bawaan'.

Alasan kedua balasan Anda tidak akurat adalah karena stmt berikut:

Seorang finalizer dipanggil pada GC yang menghancurkan objek Anda

Pertama, Finalisasi (dan juga GC itu sendiri) adalah non-deterministik. CLR menentukan kapan akan memanggil finalizer. yaitu pengembang / kode tidak tahu. Jika pola IDisposable diimplementasikan dengan benar (seperti yang saya posting di atas) dan GC.SuppressFinalize () telah dipanggil, Finalizer TIDAK akan dipanggil. Ini adalah salah satu alasan utama untuk menerapkan pola dengan benar. Karena hanya ada 1 utas Finalizer per proses yang dikelola, terlepas dari jumlah prosesor logis, Anda dapat dengan mudah menurunkan kinerja dengan mencadangkan atau bahkan menggantung utas Finalizer dengan lupa menelepon GC.SuppressFinalize ().

Saya telah memposting implementasi Pola Buang yang benar di blog saya: Cara Menerapkan Pola Buang dengan Benar

Dave Black
sumber
2
Apakah Anda yakin tentang menulis NoGateway = new NoGateway();dan NoGateway != null?
Cœur
1
Apakah ini merujuk ke stackoverflow.com/a/898856/3195477 ? Tidak ada jawaban sekarang diposting dengan nama 'Icey'
UuDdLrLrSs
@DaveInCaz sepertinya itu benar. Saya tidak melihat 'Es' di mana pun, tetapi konteks respons saya tampaknya diarahkan pada jawaban yang diberikan oleh tautan Anda di atas. Mungkin dia mengubah nama penggunanya?
Dave Black
@ DaveBlack keren, terima kasih. Saya baru saja mengeditnya langsung ke teks.
UuDdLrLrSs
2

1) WebClient adalah tipe yang dikelola, jadi Anda tidak perlu finalizer. Finalizer diperlukan jika pengguna Anda tidak membuang () kelas NoGateway Anda dan tipe asli (yang tidak dikumpulkan oleh GC) perlu dibersihkan setelahnya. Dalam hal ini, jika pengguna tidak memanggil Buang (), WebClient yang terkandung akan dibuang oleh GC tepat setelah NoGateway melakukannya.

2) Secara tidak langsung ya, tetapi Anda tidak perlu khawatir tentang hal itu. Kode Anda benar sebagai tegakan dan Anda tidak dapat mencegah pengguna Anda lupa untuk Membuang () dengan sangat mudah.

Jesse C. Slicer
sumber
2

Pola dari msdn

public class BaseResource: IDisposable
{
   private IntPtr handle;
   private Component Components;
   private bool disposed = false;
   public BaseResource()
   {
   }
   public void Dispose()
   {
      Dispose(true);      
      GC.SuppressFinalize(this);
   }
   protected virtual void Dispose(bool disposing)
   {
      if(!this.disposed)
      {        
         if(disposing)
         {
            Components.Dispose();
         }         
         CloseHandle(handle);
         handle = IntPtr.Zero;
       }
      disposed = true;         
   }
   ~BaseResource()      
   {      Dispose(false);
   }
   public void DoSomething()
   {
      if(this.disposed)
      {
         throw new ObjectDisposedException();
      }
   }
}
public class MyResourceWrapper: BaseResource
{
   private ManagedResource addedManaged;
   private NativeResource addedNative;
   private bool disposed = false;
   public MyResourceWrapper()
   {
   }
   protected override void Dispose(bool disposing)
   {
      if(!this.disposed)
      {
         try
         {
            if(disposing)
            {             
               addedManaged.Dispose();         
            }
            CloseHandle(addedNative);
            this.disposed = true;
         }
         finally
         {
            base.Dispose(disposing);
         }
      }
   }
}
devnull
sumber
1
using(NoGateway objNoGateway = new NoGateway())

setara dengan

try
{
    NoGateway = new NoGateway();
}

finally
{
    NoGateway.Dispose();
}

Seorang finalizer dipanggil pada GC yang menghancurkan objek Anda. Ini bisa pada waktu yang sama sekali berbeda dari ketika Anda meninggalkan metode Anda. Buang IDisposable dipanggil segera setelah Anda meninggalkan blok menggunakan. Karenanya polanya biasanya digunakan untuk membebaskan sumber daya segera setelah Anda tidak membutuhkannya lagi.

Daniel Fabian
sumber
1
Seorang finalizer tidak dipanggil pada GC yang menghancurkan objek. Jika "Finalisasi" diganti, maka ketika GC akan menghancurkan objek , itu akan ditempatkan pada antrian objek yang membutuhkan finalisasi, untuk sementara membuat referensi yang kuat untuk itu dan - setidaknya untuk sementara - "menghidupkan kembali" itu.
supercat
-5

Dari apa yang saya tahu, sangat disarankan untuk TIDAK menggunakan Finalizer / Destructor:

public ~MyClass() {
  //dont use this
}

Sebagian besar, ini karena tidak tahu kapan atau JIKA itu akan dipanggil. Metode buang jauh lebih baik, terutama jika Anda menggunakan atau membuang langsung.

menggunakan itu baik. Gunakan :)

Nic Wise
sumber
2
Anda harus mengikuti tautan dalam jawaban peti mati. Ya, menggunakan / Buang lebih baik tetapi kelas Disposable pasti harus mengimplementasikan keduanya.
Henk Holterman
Menarik, semua dokumen yang saya baca dari microsoft - misalnya pedoman desain kerangka kerja - katakan jangan PERNAH menggunakan destruktor. Selalu gunakan IDisposable.
Nic Wise
5
Hanya membedakan antara menggunakan kelas dan menulis kelas, baca lagi.
Henk Holterman
stackoverflow.com/questions/2605412/... dapat membantu
Alex Nolasco