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.
' tidak-dijalankan
Class SumberData
Implements IDisposable
Public Sub Dispose() Implements IDisposable.Dispose
' lepaskan resource di sini, misalnya menutup koneksi
End Sub
End ClassImplementasi 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.
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 ModuleResource '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 DisposeTiga 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.
' 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.
' 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 UsingResource 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.
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 ModuleNilai ke-1: 80
Nilai ke-2: 90
Nilai ke-3: 100
Rata-rata: 90Rata-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#
- Using Statement (Visual Basic) — Microsoft Learn — Sintaks blok Using pada VB dan perilaku disposisinya.
- IDisposable Interface — Microsoft Learn — Kontrak antarmuka dengan satu metode Dispose.
- Using objects that implement IDisposable — Microsoft Learn — Kebijakan pemakaian Using dan pola pelepasan resource pada .NET.
- How to: Dispose of a System Resource — Microsoft Learn — Contoh pelepasan resource sistem dengan Using pada VB.