Using dan Disposisi

Pembersihan resource dengan blok Using dan pola IDisposable.

Daftar isi

Resource seperti berkas, koneksi basis data, dan penampung grafis harus dilepaskan secara eksplisit setelah selesai digunakan, karena mekanisme pengelolaan memori tidak menutupnya secara otomatis. Proses pelepasan ini disebut disposisi (disposal) dan dilakukan dengan memanggil metode Dispose. Topik ini membahas pola antarmuka IDisposable, blok Using yang menjamin disposisi meskipun exception terjadi, serta penggunaannya pada penulisan dan pembacaan berkas.

Resource yang tidak ditangani pengumpul sampah#

Pengumpul sampah (garbage collector) hanya mengelola memori. Ketika sebuah objek tidak lagi dirujuk, memorinya boleh direbut kembali, tetapi berkas yang dibukanya tidak serta-merta tertutup pada saat itu juga. Waktu pembersihannya tidak dapat dijanjikan, sehingga berkas dapat tetap terkunci dan koneksi tetap terbuka jauh lebih lama daripada yang diperlukan program. Untuk resource semacam itu, .NET menyediakan antarmuka IDisposable sebagai kontrak pelepasan yang dipanggil secara eksplisit oleh kode yang memakainya.

Kelas yang membuka resource wajib menyediakan jalur penutupan yang pasti, sedangkan kode pemakainya wajib memanggilnya. Perjanjian dua arah itulah yang diatur oleh IDisposable, dan VB membungkus sisi pemanggilan Dispose dalam satu pernyataan khusus.

Pola IDisposable#

Antarmuka IDisposable hanya menuntut satu metode, yaitu Dispose tanpa parameter dan tanpa nilai kembalian. Kelas yang mengelola resource mengimplementasikan metode tersebut dengan menutup berkas, melepaskan koneksi, atau membersihkan apa pun yang dimilikinya.

vb Contoh: pola kelas yang mengimplementasikan IDisposable
' tidak-dijalankan
Class SumberData
    Implements IDisposable

    Public Sub Dispose() Implements IDisposable.Dispose
        ' lepaskan resource di sini, misalnya menutup koneksi
    End Sub
End Class

Implementasi yang baik juga tahan dipanggil lebih dari satu kali, misalnya dengan penanda Boolean yang melompati pekerjaan ketika Dispose dijalankan lagi. Perilaku ini penting karena pemanggilan Dispose yang kedua tidak boleh menimbulkan efek apa pun, apalagi memunculkan error. Pemanggil, di sisi lain, tidak perlu mengurus hal itu sendiri apabila menggunakan blok Using yang dibahas pada bagian berikutnya.

Blok Using dan End Using#

Blok Using memastikan Dispose dipanggil tepat ketika eksekusi mencapai End Using, baik alurnya selesai normal maupun terputus karena exception. Variabel resource dideklarasikan pada baris Using dan hanya berlaku di dalam bloknya, sehingga tidak ada jeda antara umurnya dan jaminan pembersihannya. Bentuk ini adalah cara baku menggunakan kelas yang mengimplementasikan IDisposable pada VB.

Program berikut menggunakan kelas contoh bernama PelacakDisposisi yang melaporkan setiap tahap hidupnya sendiri. Blok kedua sengaja melempar InvalidOperationException di tengah blok Using untuk membuktikan bahwa Dispose tetap dijalankan sebelum exception ditangani di luarnya.

vb PelacakDisposisi.vb
Option Strict On

Module Program

    ' Kelas contoh yang melaporkan setiap tahap hidupnya sendiri
    Class PelacakDisposisi
        Implements IDisposable

        Private ReadOnly nama As String
        Private sudahDibuang As Boolean = False

        Public Sub New(nama As String)
            Me.nama = nama
            Console.WriteLine($"Resource '{nama}' dibuka")
        End Sub

        Public Sub Tandai(kegiatan As String)
            Console.WriteLine($"  {nama}: {kegiatan}")
        End Sub

        Public Sub Dispose() Implements IDisposable.Dispose
            If sudahDibuang Then
                Return
            End If
            sudahDibuang = True
            Console.WriteLine($"Resource '{nama}' dilepaskan oleh Dispose")
        End Sub
    End Class

    Sub Main()
        ' pemakaian normal yang selesai tanpa gangguan
        Using pelacak As New PelacakDisposisi("arsip-1")
            pelacak.Tandai("menulis tiga baris data")
        End Using

        ' exception terjadi di tengah blok Using
        Try
            Using pelacak As New PelacakDisposisi("arsip-2")
                pelacak.Tandai("data kedua ternyata rusak")
                Throw New InvalidOperationException("data rusak")
            End Using
        Catch ex As InvalidOperationException
            Console.WriteLine($"Exception ditangani: {ex.Message}")
        End Try

        ' pemanggilan Dispose kedua tidak menjalankan apa pun lagi
        Dim ekstra As New PelacakDisposisi("arsip-3")
        ekstra.Dispose()
        ekstra.Dispose()
    End Sub
End Module
text Keluaran
Resource 'arsip-1' dibuka
  arsip-1: menulis tiga baris data
Resource 'arsip-1' dilepaskan oleh Dispose
Resource 'arsip-2' dibuka
  arsip-2: data kedua ternyata rusak
Resource 'arsip-2' dilepaskan oleh Dispose
Exception ditangani: data rusak
Resource 'arsip-3' dibuka
Resource 'arsip-3' dilepaskan oleh Dispose

Tiga baris pertama menunjukkan penggunaan normal, yaitu resource dibuka, digunakan, lalu dilepaskan tepat pada End Using. Blok kedua membuktikan jaminan utamanya, yaitu pesan pelepasan arsip-2 muncul lebih dahulu sebelum pesan penanganan exception, sehingga pembersihan tidak bergantung pada jalur eksekusi. Bagian terakhir menunjukkan bahwa pemanggilan Dispose kedua tidak mencetak apa pun karena penanda sudahDibuang menolaknya.

Bentuk setara Try/Finally dan Using bersarang#

Kompilator menerjemahkan blok Using menjadi bentuk Try/Finally yang memanggil Dispose pada Finally. Pengetahuan ini menjelaskan dari mana jaminan pembersihannya berasal, sekaligus menjadi pola yang perlu ditulis secara manual apabila resource dibuat bersyarat di luar Using.

vb Contoh: bentuk yang setara dengan blok Using
' tidak-dijalankan
Dim pembaca As New StreamReader("cadangan-nilai.txt")
Try
    ' kode yang memakai pembaca
Finally
    pembaca.Dispose()
End Try
' Blok Using menerjemahkan pola di atas secara otomatis,
' beserta pemeriksaan nilai Nothing pada variabelnya.

Apabila pekerjaan membutuhkan beberapa resource sekaligus, deklarasinya dapat digabung pada satu baris Using dengan pemisah koma. Satu End Using kemudian melepaskan semuanya dalam urutan berlawanan dari deklarasinya, sehingga resource yang dibuka terakhir ditutup lebih dahulu.

vb Contoh: dua resource pada satu blok Using
' tidak-dijalankan
Using penulis As New StreamWriter("a.txt"), pembaca As New StreamReader("b.txt")
    ' kedua resource dilepaskan pada End Using,
    ' dimulai dari pembaca yang dideklarasikan terakhir
End Using

Resource nyata dengan StreamWriter dan StreamReader#

Kelas StreamWriter dan StreamReader pada namespace System.IO adalah contoh IDisposable yang digunakan sehari-hari. Keduanya membuka berkas pada konstruktornya dan menutupnya pada Dispose. Program berikut menulis arsip cadangan nilai ujian, menutupnya pada End Using, lalu membaca kembali isi arsip yang sama di dalam blok Using kedua.

vb CadanganNilai.vb
Option Strict On

Imports System.IO

Module Program

    Sub Main()
        ' menulis arsip cadangan nilai ujian, lalu menutupnya pada End Using
        Using penulis As New StreamWriter("cadangan-nilai.txt")
            penulis.WriteLine("80")
            penulis.WriteLine("90")
            penulis.WriteLine("100")
        End Using

        ' membaca kembali isi arsip yang baru saja ditulis
        Using pembaca As New StreamReader("cadangan-nilai.txt")
            Dim jumlah As Integer = 0
            Dim total As Integer = 0
            While Not pembaca.EndOfStream
                Dim baris As String = pembaca.ReadLine()
                Dim nilai As Integer = Integer.Parse(baris)
                total += nilai
                jumlah += 1
                Console.WriteLine($"Nilai ke-{jumlah}: {nilai}")
            End While
            Console.WriteLine($"Rata-rata: {total \ jumlah}")
        End Using
    End Sub
End Module
text Keluaran
Nilai ke-1: 80
Nilai ke-2: 90
Nilai ke-3: 100
Rata-rata: 90

Rata-rata 90 sesuai perhitungan manual dari 80, 90, dan 100. Yang lebih penting, berkas cadangan-nilai.txt tertutup rapat sebelum blok pembaca dimulai, sehingga penulis dan pembaca tidak berebut berkas yang sama. Tanpa End Using yang menutup penulisnya, sebagian teks bahkan dapat belum tersimpan ke berkas karena penulisan ditahan dalam buffer terlebih dahulu.

Kesalahan umum#

Kesalahan pertama adalah menggunakan resource sesudah blok Using selesai. Apabila variabelnya dideklarasikan di luar blok, kode setelahnya masih dapat merujuknya, tetapi pemanggilan apa pun setelah disposisi berisiko memunculkan ObjectDisposedException. Deklarasikan resource langsung pada baris Using agar lingkupnya tidak lebih lebar dari umurnya.

Kesalahan kedua adalah mengandalkan pengumpul sampah untuk menutup berkas. Objeknya memang akhirnya terkumpul, tetapi waktunya tidak dapat dijanjikan, dan berkas yang masih terbuka membuat program lain gagal membacanya. Panggil disposisi secara eksplisit melalui Using pada setiap resource yang mengimplementasikan IDisposable.

Kesalahan ketiga adalah membuat kelas ber-resource tanpa mengimplementasikan IDisposable. Kode pemakainya tidak memiliki cara baku untuk melepaskannya, dan kebocorannya menumpuk tanpa terlihat pada antarmuka kelas. Setiap kelas yang membuka berkas, koneksi, atau pegangan sistem lain sebaiknya menyediakan Dispose yang mematuhi kontrak tersebut.

Ringkasan#

  • Resource seperti berkas dan koneksi harus dilepaskan secara eksplisit melalui Dispose, karena pengumpul sampah hanya mengelola memori dan waktunya tidak dapat dijanjikan.
  • Blok Using menjamin Dispose dipanggil pada End Using, termasuk ketika exception terjadi di tengah blok, dan bentuk setaranya adalah Try/Finally yang memanggil Dispose pada Finally.
  • Kelas yang mengelola resource mengimplementasikan IDisposable dengan Dispose yang tahan dipanggil ulang, sedangkan kode pemakainya mendeklarasikan resource langsung pada baris Using agar lingkupnya pas dengan umurnya.

Referensi#