Menu

Pernyataan using di C#: IDisposable, Dispose, dan using var

Pernyataan using menjamin Dispose dipanggil pada file, stream, koneksi dan objek IDisposable lainnya, bahkan saat exception dilempar. Pelajari hasil kompilasinya, urutan objek di-dispose, using declaration di C# 8, dan cara mengimplementasikan IDisposable di class milikmu sendiri.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

Beberapa objek memegang resource yang tidak dikelola oleh garbage collector: file yang terbuka, socket jaringan, koneksi database, handle sistem operasi. Objek seperti itu mengimplementasikan IDisposable, dan kamu melepas resource-nya dengan memanggil Dispose(). Pernyataan using memanggilnya untukmu di titik yang pasti, bahkan kalau exception dilempar.

Halaman ini membahas pernyataan using. Baris using System; di bagian atas file adalah fitur yang berbeda, yaitu using directive, yang mengimpor sebuah namespace.

Blok using

using (declaration) { body } membuat objek, menjalankan isi blok, lalu memanggil Dispose. Class kecil yang mencetak sesuatu dari Dispose menunjukkan dengan tepat kapan itu terjadi:

Output:

open db
db sends SELECT 1
close db
open api
api sends GET /orders
close api
error handled

Blok kedua melempar exception, dan "close api" tetap tercetak, sebelum catch menangani exception-nya. Jaminan inilah alasan memakai using alih-alih memanggil Dispose() sendiri di baris terakhir: pemanggilan manual akan terlewati oleh exception apa pun yang dilempar sebelumnya.

Hasil kompilasi using

Compiler mengembangkan blok using menjadi try/finally. Dua kode ini setara:

using (var writer = new StreamWriter("log.txt"))
{
    writer.WriteLine("started");
}

// is compiled as:
{
    var writer = new StreamWriter("log.txt");
    try
    {
        writer.WriteLine("started");
    }
    finally
    {
        if (writer != null) ((IDisposable)writer).Dispose();
    }
}

Ada tiga detail yang mengikuti dari hasil pengembangan itu. Variabelnya hanya berlaku di dalam blok dan bersifat read-only di sana (kamu tidak bisa mengisi ulang writer). Objeknya harus mengimplementasikan IDisposable, kalau tidak kodenya tidak bisa dikompilasi. Dan nilai null diizinkan: Dispose cukup dilewati, yang berguna saat sebuah factory mungkin tidak mengembalikan apa pun.

File dan stream

Class file dan stream adalah kasus sehari-hari. StreamWriter menampung teks di buffer memori, dan Dispose menulis isi buffer itu ke disk lalu menutup file handle:

Output:

id,total
1001,59.90
1002,12.50

Tanpa Dispose, baris terakhir yang ditulis writer mungkin masih tertahan di buffer-nya, dan di Windows handle yang masih terbuka mencegah program lain (dan File.Delete milikmu sendiri berikutnya) menyentuh file itu. Helper seperti File.WriteAllText dan File.ReadAllLines membuka dan melakukan dispose di dalamnya, jadi tidak butuh using. Lihat file untuk penjelasan lebih lanjut.

Beberapa resource: urutan dispose

Tumpuk pernyataan using tanpa kurung kurawal di antaranya untuk membuka beberapa resource dalam satu blok. Semuanya di-dispose dalam urutan kebalikan dari pembuatannya:

Output:

acquire file
acquire buffer
acquire writer
working
release writer
release buffer
release file
acquire a
acquire b
both open
release b
release a

Urutan terbalik inilah yang dibutuhkan resource berlapis: writer membungkus buffer yang membungkus file, jadi writer harus menulis isinya ke buffer sebelum buffer menulis ke file, sebelum file ditutup.

Using declaration (C# 8)

C# 8 menambahkan bentuk tanpa kurung kurawal. Variabelnya di-dispose saat scope di sekitarnya berakhir, biasanya di akhir method:

static void ExportReport(string path, IEnumerable<string> rows)
{
    using var writer = new StreamWriter(path);
    writer.WriteLine("Report");

    foreach (string row in rows)
    {
        writer.WriteLine(row);
    }
} // writer.Dispose() runs here, at the end of the method

Bentuk ini menghilangkan satu tingkat indentasi, yang terasa manfaatnya saat sebuah method memakai dua atau tiga resource. Aturan urutan dispose-nya sama: deklarasi dalam satu scope di-dispose dalam urutan terbalik. Konsekuensinya, resource tetap terbuka sampai scope berakhir, jadi saat sebuah file perlu ditutup sebelum method melakukan pekerjaan lain, tetap pakai bentuk blok atau letakkan deklarasinya di blok { } tersendiri.

Untuk objek yang pembersihannya asynchronous (IAsyncDisposable, seperti banyak koneksi database dan stream), C# 8 juga punya await using, yang menunggu DisposeAsync() di tempat yang sama.

Mengimplementasikan IDisposable di class milikmu sendiri

Implementasikan IDisposable saat class-mu memiliki sesuatu yang disposable (class itu membuat stream, timer, atau koneksi) sehingga harus ikut melakukan dispose. Untuk kasus yang umum, cukup sebuah method pendek:

Output:

1. user ana logged in
2. order 1001 created
AuditLog disposed
Caught ObjectDisposedException

Aturan yang diikuti kode ini: Dispose melakukan dispose pada semua yang dimiliki objek; memanggilnya lebih dari sekali tidak berpengaruh pada pemanggilan kedua; dan memakai objek setelah di-dispose melempar ObjectDisposedException. using (log) juga menunjukkan bahwa blok bisa menerima variabel yang sudah ada, bukan hanya deklarasi.

"Dispose pattern" yang lebih panjang, dengan method protected virtual void Dispose(bool disposing) dan finalizer, hanya dibutuhkan saat sebuah class langsung memegang handle unmanaged (pointer mentah dari kode native). Di .NET modern, bungkus handle seperti itu dalam subclass SafeHandle, dan versi sederhana di atas sudah cukup untuk class-mu.

Kesalahan umum

  • Mengembalikan objek yang dibuat di blok using. Objek itu di-dispose saat blok selesai, jadi pemanggil menerima stream yang sudah ditutup. Kembalikan datanya, atau biarkan pemanggil yang memegang using.
  • Lupa using pada stream dan writer. Data di buffer bisa hilang dan file tetap terkunci.
  • Melakukan dispose pada objek yang dipakai bersama. Dispose hanya yang kamu miliki. HttpClient yang berumur panjang, misalnya, memang dimaksudkan untuk dipakai bersama dan dipakai ulang, bukan dibuat lalu di-dispose di setiap request.
  • Memakai objek setelah bloknya selesai. Method pada objek yang sudah di-dispose melempar ObjectDisposedException.
  • Mengandalkan garbage collector. Garbage collector membebaskan memori, bukan file atau socket, dan tidak pernah memanggil Dispose.

Pertanyaan yang sering diajukan

Apa fungsi pernyataan using di C#?

using (var x = ...) { ... } memanggil x.Dispose() saat blok berakhir, entah berakhir dengan normal, lewat return, atau karena exception dilempar. Compiler mengubahnya menjadi try/finally dengan pemanggilan Dispose di dalam finally, jadi resource dilepas di titik yang jelas, bukan kapan pun garbage collector kebetulan berjalan.

Apa itu IDisposable di C#?

IDisposable adalah interface dengan satu method, void Dispose(). Sebuah class mengimplementasikannya saat class itu memegang sesuatu yang harus dilepas secara eksplisit: file handle, socket jaringan, koneksi database, timer. Setiap objek yang tipenya mengimplementasikan IDisposable sebaiknya di-dispose saat kamu selesai memakainya, biasanya dengan using.

Apa bedanya using dan using var?

using (var x = ...) { } melakukan dispose di akhir bloknya. Using declaration using var x = ...; (C# 8 dan sesudahnya) tidak punya blok sendiri: dispose terjadi saat scope di sekitarnya berakhir, biasanya di akhir method. Bentuk ini mengurangi tingkat sarang saat sebuah resource perlu hidup sampai akhir method.

Dalam urutan apa beberapa objek using di-dispose?

Dalam urutan kebalikan dari pembuatannya. Dengan using (var a = ...) using (var b = ...) { }, b di-dispose lebih dulu, lalu a. Itulah urutan yang kamu butuhkan saat b bergantung pada a, misalnya StreamWriter yang membungkus FileStream.

Apakah garbage collector memanggil Dispose?

Tidak. Garbage collector membebaskan memori, dan mungkin menjalankan finalizer di suatu waktu yang tidak bisa ditebak, tetapi tidak pernah memanggil Dispose. File yang tidak di-dispose bisa tetap terkunci, dan data yang masih di buffer mungkin tidak pernah ditulis. Karena itulah using ada.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai