Menerapkan metode Dispose

Metode Dispose ini terutama diimplementasikan untuk merilis sumber daya yang tidak dikelola. Saat bekerja dengan anggota instans yang merupakan implementasi IDisposable, panggilan Dispose berjenjang merupakan hal yang umum. Ada alasan lain untuk menerapkan Dispose, misalnya, untuk membebaskan memori yang dialokasikan, menghapus item yang ditambahkan ke koleksi, atau memberi sinyal pelepasan kunci yang diperoleh.

Pengumpul sampah .NET tidak mengalokasikan atau melepaskan memori yang tidak dikelola. Pola untuk membuang objek, yang disebut sebagai pola buang, memberlakukan urutan pada masa pakai objek. Pola pembuangan digunakan untuk objek yang mengimplementasikan IDisposable antarmuka. Pola ini umum terjadi saat berinteraksi dengan handel file dan pipa, handel registri, handel tunggu, atau pointer ke blok memori yang tidak dikelola, karena pengumpul sampah tidak dapat mengklaim kembali objek yang tidak dikelola.

Untuk membantu memastikan bahwa sumber daya selalu dibersihkan dengan tepat, Dispose metode harus idempoten, sehingga dapat dipanggil beberapa kali tanpa melemparkan pengecualian. Selanjutnya, pemanggilan Dispose berikutnya tidak boleh melakukan apa-apa.

Contoh kode yang disediakan untuk metode menunjukkan GC.KeepAlive bagaimana pengumpulan sampah dapat menyebabkan finalizer berjalan saat referensi yang tidak dikelola ke objek atau anggotanya masih digunakan. Mungkin masuk akal untuk menggunakan GC.KeepAlive untuk membuat objek tidak memenuhi syarat untuk pengumpulan sampah dari awal rutinitas saat ini ke titik di mana metode ini dipanggil.

Tip

Berkenaan dengan injeksi dependensi, saat mendaftarkan layanan di IServiceCollection, masa hidup layanan dikelola secara implisit atas nama Anda. IServiceProvider dan IHost yang sesuai mengatur pembersihan sumber daya. Secara khusus, implementasi IDisposable dan IAsyncDisposable dibuang dengan benar pada akhir masa pakai yang ditentukan.

Untuk informasi selengkapnya, lihat Injeksi dependensi.

Handel aman

Menulis kode untuk finalizer objek adalah tugas kompleks yang dapat menyebabkan masalah jika tidak dilakukan dengan benar. Oleh karena itu, kami sarankan Anda membuat System.Runtime.InteropServices.SafeHandle objek dan bukan menerapkan finalizer.

System.Runtime.InteropServices.SafeHandle adalah jenis terkelola abstrak yang membungkus System.IntPtr yang mengidentifikasi sumber daya yang tidak dikelola. Pada Windows mungkin mengidentifikasi handel, dan di Unix, pendeskripsi file. SafeHandle menyediakan semua logika yang diperlukan untuk memastikan bahwa sumber daya ini dirilis sekali dan hanya sekali, baik ketika SafeHandle dibuang dari atau ketika semua referensi ke SafeHandle telah dihilangkan dan SafeHandle instans diselesaikan.

System.Runtime.InteropServices.SafeHandle adalah kelas dasar abstrak. Kelas turunan menyediakan instans tertentu untuk berbagai jenis handel. Kelas turunan ini memvalidasi nilai apa untuk System.IntPtr yang dianggap tidak valid dan bagaimana sebenarnya membebaskan pegangannya. Misalnya, SafeFileHandle berasal dari SafeHandle untuk membungkus IntPtrs yang mengidentifikasi pegangan/deskriptor file yang terbuka, dan menggantikan metode SafeHandle.ReleaseHandle() untuk menutupnya (melalui fungsi close di Unix atau fungsi CloseHandle di Windows). Sebagian besar API di pustaka .NET yang membuat sumber daya yang tidak dikelola membungkusnya dalam SafeHandle dan mengembalikannya SafeHandle sesuai kebutuhan, daripada menyerahkan kembali pointer mentah. Dalam situasi di mana Anda berinteraksi dengan komponen yang tidak dikelola dan mendapatkan IntPtr untuk sumber daya yang tidak dikelola, Anda dapat membuat jenis SafeHandle Anda sendiri untuk membungkusnya. Akibatnya, beberapa non-jenisSafeHandle perlu menerapkan finalizer. Sebagian besar implementasi pola sekali pakai hanya akhirnya membungkus sumber daya terkelola lainnya, beberapa di antaranya mungkin objek SafeHandle .

Kelas turunan berikut di Microsoft.Win32.SafeHandles namespace layanan menyediakan handel yang aman.

Kelas Sumber daya yang dipegangnya
SafeFileHandle
SafeMemoryMappedFileHandle
SafePipeHandle
File, file yang dipetakan memori, dan pipa
SafeMemoryMappedViewHandle Tampilan memori
SafeNCryptKeyHandle
SafeNCryptProviderHandle
SafeNCryptSecretHandle
Konstruksi kriptografi
SafeRegistryHandle Kunci registri
SafeWaitHandle Handel tunggu

Dispose() dan Dispose(bool)

Antarmuka IDisposable memerlukan implementasi metode tanpa parameter tunggal, Dispose. Selain itu, kelas yang tidak disegel harus memiliki Dispose(bool) metode kelebihan beban.

Tanda tangan metode adalah:

  • publicnonvirtual (NotOverridable dalam Visual Basic) (IDisposable.Dispose implementasi).
  • protected virtual(Overridable dalam Visual Basic) Dispose(bool).

Metode Dispose()

publicKarena , non-virtual (NotOverridable dalam Visual Basic), metode tanpa Dispose parameter dipanggil ketika tidak lagi diperlukan (oleh konsumen jenis), tujuannya adalah untuk membebaskan sumber daya yang tidak dikelola, melakukan pembersihan umum, dan untuk menunjukkan bahwa finalizer, jika ada, tidak perlu dijalankan. Membebaskan memori sebenarnya yang terkait dengan objek terkelola selalu menjadi domain pengumpul sampah. Karena itu, ia memiliki implementasi standar:

public void Dispose()
{
    // Dispose of unmanaged resources.
    Dispose(true);
    // Suppress finalization.
    GC.SuppressFinalize(this);
}
Public Sub Dispose() _
    Implements IDisposable.Dispose
    ' Dispose of unmanaged resources.
    Dispose(True)
    ' Suppress finalization.
    GC.SuppressFinalize(Me)
End Sub

Metode Dispose melakukan semua pembersihan objek, sehingga pengumpul sampah tidak perlu lagi memanggil penggantian Object.Finalize objek. Oleh karena itu, panggilan ke metode SuppressFinalize mencegah pengumpul sampah menjalankan finalizer. Jika jenis tidak memiliki finalizer, panggilan ke GC.SuppressFinalize tidak berpengaruh. Pembersihan aktual dilakukan oleh Dispose(bool) metode kelebihan beban.

Overload metode Dispose(bool)

Dalam overload, parameter disposing adalah Boolean yang menunjukkan apakah pemanggilan metode berasal dari metode Dispose (nilainya true) atau dari finalizer (nilainya false).

protected virtual void Dispose(bool disposing)
{
    if (_disposed)
    {
        return;
    }

    if (disposing)
    {
        // TODO: dispose managed state (managed objects).
    }

    // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
    // TODO: set large fields to null.

    _disposed = true;
}
Protected Overridable Sub Dispose(disposing As Boolean)
     If disposed Then Exit Sub	

     ' A block that frees unmanaged resources.
     
     If disposing Then
         ' Deterministic call…
         ' A conditional block that frees managed resources.    	
     End If
     
     disposed = True
End Sub

Penting

Parameter disposing harus false saat dipanggil dari finalizer, dan true saat dipanggil dari metode IDisposable.Dispose. Dengan kata lain, ini adalah true ketika dipanggil secara deterministik dan false ketika dipanggil secara nondeterministik.

Isi metode terdiri dari tiga blok kode:

  • Blok untuk pengembalian kondisional jika objek sudah dibuang.

  • Blok yang membebaskan sumber daya yang tidak terkelola. Blok ini dijalankan terlepas dari nilai parameter disposing.

  • Blok bersyarat yang membebaskan sumber daya terkelola. Blok ini dijalankan jika nilai disposing adalah true. Sumber daya terkelola yang gratis dapat mencakup:

    • Objek terkelola yang mengimplementasikan IDisposable. Blok bersyarat dapat digunakan untuk memanggil implementasi Dispose (cascade dispose). Jika Anda telah menggunakan kelas turunan System.Runtime.InteropServices.SafeHandle untuk membungkus sumber daya yang tidak dikelola, Anda harus memanggil implementasi SafeHandle.Dispose() di sini.

    • Objek terkelola yang menghabiskan banyak memori atau menghabiskan sumber daya yang langka. Tetapkan referensi objek terkelola besar ke null untuk membuatnya lebih mungkin tidak dapat dijangkau. Ini melepaskannya lebih cepat daripada jika mereka direklamasi secara nondeterministik.

Jika panggilan metode berasal dari finalizer, hanya kode yang membebaskan sumber daya yang tidak terkelola yang harus dijalankan. Pelaksana bertanggung jawab untuk memastikan bahwa jalur palsu tidak berinteraksi dengan objek terkelola yang mungkin telah dibuang. Ini penting karena urutan pengumpul sampah membuang objek terkelola selama finalisasi tidak deterministik.

Panggilan buang kaskade

Jika kelas Anda memiliki bidang atau properti dan jenisnya mengimplementasikan IDisposable, kelas yang berisi itu sendiri juga harus menerapkan IDisposable. Kelas yang membuat instans IDisposable implementasi dan menyimpannya sebagai anggota instans juga bertanggung jawab atas pembersihannya. Ini membantu memastikan bahwa jenis sekali pakai yang dirujuk diberi kesempatan untuk melakukan pembersihan Dispose secara deterministik melalui metode . Dalam contoh berikut, kelasnya adalah sealed (atau NotInheritable di Visual Basic).

using System;

public sealed class Foo : IDisposable
{
    private readonly IDisposable _bar;

    public Foo()
    {
        _bar = new Bar();
    }

    public void Dispose() => _bar.Dispose();
}
Public NotInheritable Class Foo
    Implements IDisposable

    Private ReadOnly _bar As IDisposable

    Public Sub New()
        _bar = New Bar()
    End Sub

    Public Sub Dispose() Implements IDisposable.Dispose
        _bar.Dispose()
    End Sub
End Class

Tip

  • Jika kelas Anda memiliki bidang atau properti tetapi tidakmemilikinyaIDisposable, yang berarti kelas tidak membuat objek, maka kelas tidak perlu menerapkan IDisposable.
  • Ada kasus ketika Anda mungkin ingin melakukan null-checking di finalizer (yang mencakup Dispose(false) metode yang dipanggil oleh finalizer). Salah satu alasan utamanya adalah jika Anda tidak yakin apakah instans diinisialisasi sepenuhnya (misalnya, pengecualian mungkin dilemparkan ke dalam konstruktor).

Menerapkan pola pembuangan

Semua kelas yang tidak disegel (atau kelas Visual Basic tidak dimodifikasi sebagai NotInheritable) harus dianggap sebagai kelas dasar potensial, karena dapat diwariskan. Jika Anda menerapkan pola buang untuk kelas dasar potensial, Anda harus memberikan hal berikut:

  • Implementasi Dispose yang memanggil metode Dispose(bool).
  • Metode Dispose(bool) yang melakukan pembersihan aktual.
  • Baik kelas yang berasal dari SafeHandle yang membungkus sumber daya Anda yang tidak terkelola (disarankan), atau penimpaan ke metode Object.Finalize. Kelas ini SafeHandle menyediakan finalizer, jadi Anda tidak perlu menulisnya sendiri.

Penting

Dimungkinkan bagi kelas dasar untuk hanya mereferensikan objek terkelola dan menerapkan pola pembuangan. Dalam kasus ini, finalizer tidak perlu. Finalizer hanya diperlukan jika Anda langsung mereferensikan sumber daya yang tidak dikelola.

Berikut adalah contoh umum penerapan pola pembuangan untuk kelas dasar yang menggunakan handel yang aman.

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

public class BaseClassWithSafeHandle : IDisposable
{
    // To detect redundant calls
    private bool _disposedValue;

    // Instantiate a SafeHandle instance.
    private SafeHandle? _safeHandle = new SafeFileHandle(IntPtr.Zero, true);

    // Public implementation of Dispose pattern callable by consumers.
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    // Protected implementation of Dispose pattern.
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            if (disposing)
            {
                _safeHandle?.Dispose();
                _safeHandle = null;
            }

            _disposedValue = true;
        }
    }
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices

Public Class BaseClassWithSafeHandle
    Implements IDisposable

    ' To detect redundant calls
    Private _disposedValue As Boolean

    ' Instantiate a SafeHandle instance.
    Private _safeHandle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)

    ' Public implementation of Dispose pattern callable by consumers.
    Public Sub Dispose() _
               Implements IDisposable.Dispose
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub

    ' Protected implementation of Dispose pattern.
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not _disposedValue Then

            If disposing Then
                _safeHandle?.Dispose()
                _safeHandle = Nothing
            End If

            _disposedValue = True
        End If
    End Sub
End Class

Catatan

Contoh sebelumnya menggunakan objek SafeFileHandle untuk mengilustrasikan pola; objek apa pun yang berasal dari SafeHandle dapat digunakan sebagai gantinya. Perhatikan bahwa contoh tidak membuat instans objek SafeFileHandle dengan benar.

Berikut adalah pola umum untuk menerapkan pola pembuangan untuk kelas dasar yang mengambil alih Object.Finalize.

using System;

public class BaseClassWithFinalizer : IDisposable
{
    // To detect redundant calls
    private bool _disposedValue;

    ~BaseClassWithFinalizer() => Dispose(false);

    // Public implementation of Dispose pattern callable by consumers.
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    // Protected implementation of Dispose pattern.
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            if (disposing)
            {
                // TODO: dispose managed state (managed objects)
            }

            // TODO: free unmanaged resources (unmanaged objects) and override finalizer
            // TODO: set large fields to null
            _disposedValue = true;
        }
    }
}
Public Class BaseClassWithFinalizer
    Implements IDisposable

    ' To detect redundant calls
    Private _disposedValue As Boolean

    Protected Overrides Sub Finalize()
        Dispose(False)
    End Sub

    ' Public implementation of Dispose pattern callable by consumers.
    Public Sub Dispose() _
               Implements IDisposable.Dispose
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub

    ' Protected implementation of Dispose pattern.
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not _disposedValue Then

            If disposing Then
                ' TODO: dispose managed state (managed objects)
            End If

            ' TODO free unmanaged resources (unmanaged objects) And override finalizer
            ' TODO: set large fields to null
            _disposedValue = True
        End If
    End Sub
End Class

Tip

Dalam C#, Anda menerapkan finalisasi dengan menyediakan finalizer, bukan dengan mengambil alih Object.Finalize. Di Visual Basic, Anda membuat finalizer dengan Protected Overrides Sub Finalize().

Menerapkan pola pembuangan untuk kelas turunan

Kelas yang diturunkan dari kelas yang mengimplementasikan antarmuka IDisposable tidak boleh mengimplementasikan IDisposable, karena implementasi kelas dasar IDisposable.Dispose diwarisi oleh kelas turunannya. Sebagai gantinya, untuk membersihkan kelas turunan, Anda memberikan hal berikut:

  • Metode protected override void Dispose(bool) yang mengambil alih metode kelas dasar dan melakukan pembersihan aktual dari kelas turunan. Metode ini juga harus memanggil metode base.Dispose(bool) (MyBase.Dispose(bool) dalam Visual Basic) dengan meneruskan status pembuangan (parameter bool disposing) sebagai argumen.
  • Baik kelas yang berasal dari SafeHandle yang membungkus sumber daya Anda yang tidak terkelola (disarankan), atau penimpaan ke metode Object.Finalize. Kelas SafeHandle ini menyediakan finalizer yang membebaskan Anda dari harus membuat kode. Jika Anda menyediakan finalizer, finalizer ini harus memanggil overload Dispose(bool) dengan argumen false.

Berikut adalah contoh pola umum untuk menerapkan pola pembuangan untuk kelas turunan yang menggunakan handel yang aman:

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

public class DerivedClassWithSafeHandle : BaseClassWithSafeHandle
{
    // To detect redundant calls
    private bool _disposedValue;

    // Instantiate a SafeHandle instance.
    private SafeHandle? _safeHandle = new SafeFileHandle(IntPtr.Zero, true);

    // Protected implementation of Dispose pattern.
    protected override void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            if (disposing)
            {
                _safeHandle?.Dispose();
                _safeHandle = null;
            }

            _disposedValue = true;
        }

        // Call base class implementation.
        base.Dispose(disposing);
    }
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices

Public Class DerivedClassWithSafeHandle
    Inherits BaseClassWithSafeHandle

    ' To detect redundant calls
    Private _disposedValue As Boolean

    ' Instantiate a SafeHandle instance.
    Private _safeHandle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)

    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        If Not _disposedValue Then

            If disposing Then
                _safeHandle?.Dispose()
                _safeHandle = Nothing
            End If

            _disposedValue = True
        End If

        ' Call base class implementation.
        MyBase.Dispose(disposing)
    End Sub
End Class

Catatan

Contoh sebelumnya menggunakan objek SafeFileHandle untuk mengilustrasikan pola; objek apa pun yang berasal dari SafeHandle dapat digunakan sebagai gantinya. Perhatikan bahwa contoh tidak membuat instans objek SafeFileHandle dengan benar.

Berikut adalah pola umum untuk menerapkan pola pembuangan untuk kelas turunan yang mengambil alih Object.Finalize:

public class DerivedClassWithFinalizer : BaseClassWithFinalizer
{
    // To detect redundant calls
    private bool _disposedValue;

    ~DerivedClassWithFinalizer() => Dispose(false);

    // Protected implementation of Dispose pattern.
    protected override void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            if (disposing)
            {
                // TODO: dispose managed state (managed objects).
            }

            // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
            // TODO: set large fields to null.
            _disposedValue = true;
        }

        // Call the base class implementation.
        base.Dispose(disposing);
    }
}
Public Class DerivedClassWithFinalizer
    Inherits BaseClassWithFinalizer

    ' To detect redundant calls
    Private _disposedValue As Boolean

    Protected Overrides Sub Finalize()
        Dispose(False)
    End Sub

    ' Protected implementation of Dispose pattern.
    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        If Not _disposedValue Then

            If disposing Then
                ' TODO: dispose managed state (managed objects).
            End If

            ' TODO free unmanaged resources (unmanaged objects) And override a finalizer below.
            ' TODO: set large fields to null.
            _disposedValue = True
        End If

        ' Call the base class implementation.
        MyBase.Dispose(disposing)
    End Sub
End Class

Lihat juga