Tutup dan Buang - yang harus dihubungi?

Jawaban:

191

Saya ingin memperjelas situasi ini.

Menurut pedoman Microsoft, ini adalah praktik yang baik untuk memberikan Closemetode yang sesuai. Berikut ini adalah kutipan dari pedoman desain Kerangka

Pertimbangkan memberikan metode Close(), selain Dispose(), jika dekat adalah terminologi standar di daerah tersebut. Saat melakukannya, penting agar Anda membuat Closeimplementasinya identik dengan Dispose...

Dalam sebagian besar kasus Closedan Disposemetode adalah setara. Perbedaan utama antara Closedan Disposedalam hal SqlConnectionObjectadalah:

Aplikasi dapat menelepon Closelebih dari satu kali. Tidak ada pengecualian yang dihasilkan.

Jika Anda memanggil negara objek Disposemetode SqlConnectionakan diatur ulang. Jika Anda mencoba memanggil metode apa pun pada SqlConnection objek yang dibuang , Anda akan menerima pengecualian.

Yang mengatakan:

  • Jika Anda menggunakan objek koneksi satu kali, gunakan Dispose.
  • Jika objek koneksi harus digunakan kembali, gunakan Closemetode.
aku
sumber
5
@ Chris, dokumentasi untuk Tutup () mengatakan "Ini kemudian melepaskan koneksi ke kumpulan koneksi, atau menutup koneksi jika kumpulan koneksi dinonaktifkan." Jadi Tutup () harus cukup untuk menjaga kolam koneksi tidak meluap.
David Hammond
@ Davidvidam: Anda benar. Saya menghapus komentar saya sebelumnya.
NotMe
3
Apakah .Dispose () juga melepaskan koneksi kembali ke kolam?
oscilatingcretin
Ini adalah argumen terbaik yang pernah saya baca tentang masalah ini dalam satu dekade. Poin luar biasa.
Michael Erickson
1
Jadi itu bekerja seperti ini 1. con.Open() con.Close(); 2 con.Open(); // reuse 3. con.Dispose(); // use one time con.Open(); // error
shaijut
24

Seperti biasa jawabannya adalah: itu tergantung. Kelas yang berbeda menerapkan IDisposabledengan cara yang berbeda, dan terserah pada Anda untuk melakukan penelitian yang diperlukan.

Sejauh ini SqlClient, praktik yang disarankan adalah melakukan yang berikut:

using (SqlConnection conn = /* Create new instance using your favorite method */)
{
    conn.Open();
    using (SqlCommand command = /* Create new instance using your favorite method */)
    {
        // Do work
    }
    conn.Close(); // Optional
}

Anda seharusnya menelepon Dispose(atau Close*) di koneksi! Jangan tidak menunggu pengumpul sampah untuk membersihkan koneksi Anda, ini akan mengikat koneksi di kolam renang sampai siklus GC berikutnya (setidaknya). Jika Anda menelepon Dispose, tidak perlu menelepon Close, dan karena usingkonstruk membuatnya sangat mudah untuk ditangani Disposedengan benar, sebenarnya tidak ada alasan untuk meneleponClose .

Koneksi secara otomatis dikumpulkan, dan panggilan Dispose/ Closepada koneksi tidak secara fisik menutup koneksi (dalam keadaan normal). Jangan mencoba menerapkan pengumpulan Anda sendiri. SqlClientmelakukan pembersihan pada koneksi ketika itu diambil dari kolam (seperti mengembalikan konteks database dan opsi koneksi).

* jika Anda menelepon Close, pastikan untuk melakukannya dengan cara pengecualian-aman (yaitu menangkap atau akhirnya memblokir).

Brannon
sumber
Ketika Anda berkata, "Terserah Anda untuk melakukan penelitian yang diperlukan", penelitian apa itu? Satu-satunya cara saya tahu bagaimana mengatakan dengan pasti adalah melalui Refleksi tetapi itu memiliki kelemahan menjadi "ilegal" dalam kebanyakan situasi.
Badai
7
Saya tidak akan mengatakan: conn.Close(); // OptionalIni bukan opsional. Itu berlebihan dan tidak perlu. Anda membuang objek dua kali dan ini akan ditandai sebagai peringatan oleh beberapa alat analisis kode.
Metalogic
@ Metalogic Saya setuju itu berlebihan yang tidak perlu (dan jelek) untuk memanggil Tutup dengan penggunaan penggunaan yang tepat. Namun, nitpicking: memanggil Tutup bukan "membuang" (sementara Buang menyiratkan Tutup untuk koneksi SqlConnection). Bandingkan dengan using (var x = ..) { x.Dispose(); }, dalam hal ini xbenar-benar "dibuang dua kali".
user2864740
11

Anda TIDAK perlu menelepon Buang ()!

Buang () adalah untuk memanggil pengembang, Kolektor Sampah memanggil Finalize (). Jika Anda tidak memanggil Buang () pada objek Anda, setiap sumber daya yang tidak dikelola yang digunakan tidak akan dibuang sampai pengumpul sampah datang dan menyelesaikannya (dan siapa yang tahu kapan itu akan terjadi).

Skenario ini disebut Finalisasi Non Deterministik dan merupakan perangkap umum bagi pengembang .net. Jika Anda bekerja dengan objek yang menerapkan IDisposable kemudian panggil Buang () pada mereka!

http://www.ondotnet.com/pub/a/oreilly/dotnet/news/programmingCsharp_0801.html?page=last

Meskipun mungkin ada banyak contoh (seperti pada SqlConnection) di mana Anda memanggil Disponse () pada beberapa objek dan itu hanya memanggil Tutup () pada koneksi itu atau menutup pegangan file, hampir selalu merupakan taruhan terbaik Anda untuk memanggil Buang ()! kecuali Anda berencana menggunakan kembali objek dalam waktu dekat.

Tyler
sumber
26
Komentar ini sepenuhnya salah. Pengumpul sampah tidak pernah menelepon Dispose.
Stephen Cleary
3
Konsekuensi: Anda harus memanggil Dispose() jika Anda tidak menggunakan using()dengan kelas yang mengimplementasikan IDisposable. Jika kelas yang dipanggil mengimplementasikan IDisposable dan Anda telah membungkus penggunaannya pada halaman di dalamnya using(), maka Anda dapat membuangnya dengan Dispose()(pun intended, jadi tembak saya). Menggunakan Close(), bagaimanapun, direkomendasikan dengan apa pun yang secara eksplisit memanfaatkan Open(), AFAIK.
René Kåbis
Saya tidak yakin tentang DBMS lain, tetapi Anda TIDAK bisa melakukan keduanya di PostgreSql . Setelah Anda Closeterhubung, Postgres secara otomatis mengatur pengenal koneksi null. Dari sana, seseorang tidak dapat Disposepengidentifikasi koneksi sql yang sudah diatur ke null.
SSD
10

Sebab SqlConnection, dari perspektif koneksi itu sendiri, mereka setara. Menurut Reflector, Dispose()panggilanClose() serta melakukan beberapa operasi bebas memori tambahan - kebanyakan dengan menetapkan anggota sama dengan nol.

Untuk Stream, mereka sebenarnya setara. Stream.Dispose()cukup panggil Close ().

Curt Hagenlocher
sumber
1
Apakah kamu yakin MSDN mengatakan itu diwarisi dariComponent yang sepertinya tidak melakukan apa-apa untuk mencoba dan meneleponClose() . Saya tidak bisa melihat di mana pun di DBConnectionatau SqlConnectionyang terkait dengan salah satu pemberitahuan itu. Namun memiliki pribadi DisposeMe()yang tidak dirujuk di mana pun .
Deanna
@Deanna itu ditimpa di sini: github.com/dotnet/corefx/blob/…
David Cumps
@ Davidvidumps Tampaknya sudah berubah dalam 4 tahun sejak saya menulis komentar itu. Tautan saya tidak lagi valid.
Deanna
github.com/microsoft/referencesource/blob/master/System.Data/… , saya tidak melihatnya di sini
Royi Namir
6

Calon saran cepat ini menjadi jawaban panjang. Maaf.

Seperti yang ditunjukkan tyler dalam jawabannya yang bagus, sambil menelepon Dispose() adalah praktik pemrograman yang bagus. Ini karena metode ini seharusnya "menyatukan" semua sumber daya bebas yang dibutuhkan sehingga tidak ada sumber daya terbuka yang tidak dibutuhkan. Jika Anda menulis beberapa teks ke file, misalnya, dan gagal menutup file (membebaskan sumber daya), itu akan tetap terbuka dan tidak ada orang lain yang dapat menulis sampai file tersebut tiba dan melakukan apa yang seharusnya Anda miliki. selesai

Sekarang, dalam beberapa kasus akan ada metode "penyelesaian" yang lebih spesifik untuk kelas yang Anda hadapi, seperti StreamWriter.Close(), yang menimpa TextWriter.Close(). Memang mereka biasanya lebih cocok dengan situasi: seorang StreamWriter Close(), misalnya, menyiram aliran dan encoder yang mendasari sebelum Dispose()objek! Keren!

Namun, menelusuri MSDN Anda akan menemukan bahwa bahkan Microsoft kadang-kadang bingung oleh banyak penutup dan pembuangan. Di halaman web ini , misalnya, dalam beberapa contoh Close()dipanggil sebelum implisit Dispose()(lihat menggunakan pernyataan jika Anda tidak mengerti mengapa itu implisit), dan khususnya mereka tidak mau repot-repot. Kenapa bisa begitu? Saya juga bingung.

Alasan saya menemukan (dan, saya tekankan, ini adalah penelitian asli dan saya pasti akan kehilangan reputasi jika saya salah) adalah yang Close()mungkin gagal, menghasilkan pengecualian sementara membiarkan sumber daya terbuka, sementara Dispose()pasti akan membebaskan mereka . Itulah sebabnya a Dispose()harus selalu menjaga Close()panggilan (maaf untuk permainan kata-kata).

MyResource r = new MyResource();

try {
  r.Write(new Whatever());

  r.Close()
finally {
  r.Dispose();
}

Dan ya, saya kira Microsoft salah menggunakan contoh itu. Mungkin cap waktu itu tidak akan pernah masuk ke file.

Saya memperbaiki kode lama saya besok.

Edit: maaf Brannon, saya tidak bisa mengomentari jawaban Anda, tetapi apakah Anda yakin itu ide yang baik untuk panggilan Close()pada finallyblok? Saya kira pengecualian dari yang mungkin merusak sisa blok, yang kemungkinan akan berisi kode pembersihan yang penting.

Balas ke Brannon: bagus, tapi jangan lupa untuk menelepon Close()ketika itu benar-benar diperlukan (misalnya ketika berhadapan dengan stream - tidak tahu banyak tentang koneksi SQL di .NET).

André Chalella
sumber
Sebenarnya, saya tidak pernah memanggil Tutup (), saya hanya membiarkan Buang () dan konstruk 'menggunakan' melakukan hal yang benar . Jika Anda tidak menelepon Buang, maka Anda harus menelepon Tutup dengan cara pengecualian-aman. Mungkin ide yang bagus untuk menambahkan penanganan eksepsi ke blok akhirnya.
Brannon
Benar, komentar saya khusus untuk SqlClient. Intinya adalah, Anda perlu memahami kelas yang Anda gunakan. Selalu menelepon Buang belum tentu jawaban yang tepat.
Brannon
2

Typecast ke iDisposable, dan panggilan buang itu. Itu akan memanggil metode apa pun yang dikonfigurasi sebagai penerapan "iDisposable.Dispose", terlepas dari apa nama fungsinya.

supercat
sumber
Fungsi "bernama" Buang ': jadi kita kembali ke pertanyaan awal:}
user2864740
Fungsi terikat IDisposable.Dispose, tetapi itu tidak berarti itu namanya. Perhatikan bahwa di vb.net, ada kemungkinan fungsi terikat ke beberapa anggota antarmuka dengan nama yang tidak perlu terkait dengan fungsi tersebut.
supercat
Pemain seperti ini:using (myObj as IDisposable)
Yousha Aleayoub
2

Secara umum, kami menghadapi masalah dalam Tutup (), Batalkan () dan Buang () tetapi izinkan saya memberi tahu Anda perbedaan di antara mereka.

1) ABORT: - Saya tidak akan menyarankan untuk menggunakan ini karena ketika dibatalkan disebut klien akan menghapus koneksi tanpa memberitahu server sehingga server akan menunggu beberapa waktu (sekitar 1 menit). Jika Anda memiliki permintaan massal maka Anda tidak dapat menggunakan batalkan () karena dapat menyebabkan waktu habis untuk kumpulan koneksi terbatas Anda.

2) Tutup: - Tutup adalah cara yang sangat baik untuk menutup koneksi karena ketika menutup koneksi itu akan memanggil server dan mengakui server untuk menutup sisi itu juga.

Di sini, satu hal lagi untuk dilihat. Dalam beberapa kasus, jika kesalahan menghasilkan maka itu bukan cara yang baik untuk menulis kode akhirnya koneksi.close () karena pada saat itu keadaan komunikasi akan rusak.

3) Buang: - Ini adalah salah satu jenis penutupan tetapi setelah menutup koneksi Anda tidak dapat membukanya lagi.

Jadi coba cara ini,

private void CloseConnection(Client client)
    {
        if (client != null && client.State == CommunicationState.Opened)
        {
            client.Close();
        }
        else
        {
            client.Abort();
        }
    }
Jadia yang dalam
sumber
Pemeriksaan client != nulltidak benar / menyesatkan karena tidak melindungi semua penggunaan. Juga, saya tidak yakin bagaimana kode dapat mencapai "koneksi ini tidak dibuka dan harus ditutup".
user2864740