Mengatur Objek ke Null / Tidak Ada setelah digunakan dalam .NET

187

Haruskah Anda mengatur semua objek ke null( Nothingdalam VB.NET) setelah Anda selesai dengan mereka?

Saya mengerti bahwa dalam. NET adalah penting untuk membuang contoh objek yang mengimplementasikan IDisposableantarmuka untuk melepaskan beberapa sumber daya meskipun objek masih bisa menjadi sesuatu setelah dibuang (karena itu isDisposedproperti dalam bentuk), jadi saya menganggapnya masih bisa berada dalam memori atau setidaknya sebagian?

Saya juga tahu bahwa ketika sebuah objek keluar dari ruang lingkup itu kemudian ditandai untuk koleksi siap untuk lulus berikutnya dari pengumpul sampah (meskipun ini mungkin membutuhkan waktu).

Jadi dengan ini dalam pikiran akan mengaturnya untuk nullmempercepat sistem melepaskan memori karena tidak harus bekerja bahwa itu tidak lagi dalam ruang lingkup dan apakah mereka ada efek samping yang buruk?

Artikel MSDN tidak pernah melakukan ini dalam contoh dan saat ini saya melakukan ini karena saya tidak dapat melihat bahayanya. Namun saya telah menemukan campuran pendapat sehingga komentar apa pun berguna.

John
sumber
4
+1 pertanyaan bagus. Apakah ada yang tahu keadaan di mana kompiler akan mengoptimalkan tugas sama sekali? yaitu ada yang melihat MSIL dalam keadaan yang berbeda dan mencatat IL untuk menetapkan objek ke nol (atau ketiadaan).
Tim Medora

Jawaban:

73

Karl benar-benar benar, tidak perlu mengatur objek ke null setelah digunakan. Jika suatu objek mengimplementasikan IDisposable, pastikan Anda menelepon IDisposable.Dispose()ketika Anda selesai dengan objek itu (dibungkus dengan try.. finally, atau, satu using()blok). Tetapi bahkan jika Anda tidak ingat untuk menelepon Dispose(), metode finalis pada objek harus memanggil Dispose()Anda.

Saya pikir ini adalah perawatan yang baik:

Menggali IDisposable

dan ini

Memahami IDisposable

Tidak ada gunanya mencoba menebak GC dan strategi manajemennya yang kedua karena penyesuaian diri dan buram. Ada diskusi yang bagus tentang cara kerja batin dengan Jeffrey Richter di Dot Net Rocks di sini: Jeffrey Richter di Windows Memory Model dan Richters buku CLR via C # bab 20 memiliki perlakuan yang luar biasa:

Kev
sumber
6
Aturan tentang tidak menetapkan ke null bukan "keras dan cepat" ... jika objek diletakkan pada tumpukan objek besar (ukuran> 85K) itu akan membantu GC jika Anda mengatur objek ke nol ketika Anda selesai menggunakannya.
Scott Dorman
Saya setuju untuk batas yang terbatas, tetapi kecuali jika Anda mulai mengalami tekanan memori maka saya melihat tidak perlu 'mengoptimalkan secara prematur' dengan mengatur objek ke nol setelah digunakan.
Kev
21
Seluruh bisnis "jangan optimalkan prematur" ini terdengar lebih seperti "Lebih suka lambat dan jangan khawatir karena CPU semakin cepat dan aplikasi CRUD tidak memerlukan kecepatan." Mungkin aku saja. :)
BobbyShaftoe
19
Apa artinya sebenarnya adalah "Pengumpul Sampah lebih baik dalam mengelola memori daripada Anda." Itu mungkin hanya aku. :)
BobRodes
2
@ BobbyShaftoe: Mungkin salah untuk mengatakan "optimasi prematur buruk, selalu" karena melompat ke ekstrim berlawanan dari "terdengar lebih seperti 'lebih suka lambat'." Tidak ada programmer yang masuk akal juga. Ini tentang nuansa dan menjadi pintar tentang apa yang Anda optimalkan. Saya pribadi khawatir tentang kejelasan kode dan KEMUDIAN kinerja UJI SEBENARNYA karena saya pribadi melihat banyak orang (termasuk saya ketika saya masih muda) menghabiskan terlalu banyak waktu untuk membuat algoritma "sempurna", hanya untuk menghemat 0,1 ms di 100.000 iterasi semua sementara keterbacaan sepenuhnya ditembak.
Brent Rittenhouse
36

Alasan lain untuk menghindari pengaturan objek ke nol ketika Anda selesai dengan mereka adalah bahwa itu benar-benar dapat membuat mereka hidup lebih lama.

misalnya

void foo()
{
    var someType = new SomeType();
    someType.DoSomething();
    // someType is now eligible for garbage collection         

    // ... rest of method not using 'someType' ...
}

akan memungkinkan objek yang dirujuk oleh someType menjadi GC setelah panggilan ke "DoSomething" tetapi

void foo()
{
    var someType = new SomeType();
    someType.DoSomething();
    // someType is NOT eligible for garbage collection yet
    // because that variable is used at the end of the method         

    // ... rest of method not using 'someType' ...
    someType = null;
}

terkadang dapat menjaga objek tetap hidup sampai akhir metode. The JIT biasanya akan dioptimalkan pergi tugas ke nol , sehingga kedua bit kode akhir sampai menjadi sama.

Wilka
sumber
Itu poin yang menarik. Saya selalu berpikir bahwa objek tidak keluar dari ruang lingkup sampai setelah metode di mana mereka dicakup selesai. Kecuali tentu saja objek dicakup dalam blok Menggunakan atau secara eksplisit diatur ke Tidak ada atau nol.
Guru Josh
1
Cara yang lebih disukai untuk memastikan mereka tetap hidup adalah dengan menggunakan GC.KeepAlive(someType); Lihat ericlippert.com/2013/06/10/construction-destruction
NotMe
7

Juga:

using(SomeObject object = new SomeObject()) 
{
  // do stuff with the object
}
// the object will be disposed of
Steve Tranby
sumber
7

Secara umum, tidak perlu membatalkan objek setelah digunakan, tetapi dalam beberapa kasus saya menemukan ini adalah praktik yang baik.

Jika suatu objek mengimplementasikan IDisposable dan disimpan dalam bidang, saya pikir itu baik untuk membatalkannya, hanya untuk menghindari menggunakan objek yang dibuang. Bug dari jenis berikut ini bisa menyakitkan:

this.myField.Dispose();
// ... at some later time
this.myField.DoSomething();

Ada baiknya untuk membatalkan bidang setelah membuangnya, dan mendapatkan NullPtrEx tepat di baris tempat bidang tersebut digunakan lagi. Jika tidak, Anda mungkin mengalami beberapa bug cryptic di telepon (tergantung pada apa yang DoSomething lakukan).

dbkk
sumber
8
Nah, objek yang dibuang harus membuang ObjectDisposedException jika sudah dibuang. Sejauh ini yang saya tahu, ini membutuhkan kode boilerplate di semua tempat, tetapi sekali lagi, Disingkirkan adalah paradigma yang dipikirkan dengan buruk.
nicodemus13
3
Ctrl + F untuk .Dispose(). Jika Anda menemukannya, Anda tidak menggunakan IDisposable dengan benar. Satu-satunya penggunaan untuk objek sekali pakai harus dalam batas blok penggunaan. Dan setelah menggunakan-blok, Anda bahkan tidak memiliki akses myFieldlagi. Dan di dalam blok menggunakan, pengaturan ke nulltidak diperlukan, blok menggunakan akan membuang objek untuk Anda.
Suamere
7

Kemungkinannya adalah bahwa kode Anda tidak terstruktur cukup ketat jika Anda merasa perlu untuk nullvariabel.

Ada beberapa cara untuk membatasi ruang lingkup variabel:

Seperti yang disebutkan oleh Steve Tranby

using(SomeObject object = new SomeObject()) 
{
  // do stuff with the object
}
// the object will be disposed of

Demikian pula, Anda cukup menggunakan kurung keriting:

{
    // Declare the variable and use it
    SomeObject object = new SomeObject()
}
// The variable is no longer available

Saya menemukan bahwa menggunakan kurung keriting tanpa "heading" untuk benar-benar membersihkan kode dan membantu membuatnya lebih dimengerti.

mbillard
sumber
Saya mencoba menggunakan cakupan lokal kustom satu kali (kebanyakan menjadi smarta $$). Perusahaan itu meledak.
Suamere
Pada catatan lain: Ini karena kompiler c # akan menemukan variabel cakupan lokal yang mengimplementasikan IDisposable, dan akan memanggil .Dosis (MOST Of the time) ketika cakupannya berakhir. Namun ... Koneksi SQL adalah satu saat ketika .Dose () tidak pernah dioptimalkan. Ada beberapa jenis yang memerlukan perhatian eksplisit, jadi saya pribadi selalu melakukan hal-hal secara eksplisit hanya supaya saya tidak digigit.
Suamere
5

Satu-satunya waktu Anda harus menetapkan variabel ke nol adalah ketika variabel tidak keluar dari ruang lingkup dan Anda tidak lagi membutuhkan data yang terkait dengannya. Kalau tidak, tidak perlu.

Bob
sumber
2
Itu benar, tetapi itu juga berarti Anda mungkin harus memperbaiki kode Anda. Saya tidak berpikir saya pernah perlu mendeklarasikan variabel di luar ruang lingkup yang dimaksudkan.
Karl Seguin
2
Jika "variabel" dipahami untuk memasukkan bidang objek, maka jawaban ini sangat masuk akal. Dalam kasus di mana "variabel" hanya berarti "variabel lokal" (dari suatu metode), maka kita mungkin berbicara tentang kasus khusus di sini (misalnya metode yang berjalan untuk rentang waktu yang jauh lebih lama dari biasanya).
stakx - tidak lagi berkontribusi
5

Secara umum tidak perlu diatur ke nol. Tapi misalkan Anda memiliki fungsi Reset di kelas Anda.

Maka Anda mungkin melakukannya, karena Anda tidak ingin memanggil buang dua kali, karena beberapa dari pembuangan tersebut mungkin tidak diimplementasikan dengan benar dan membuang System.ObjectDosedosed exception.

private void Reset()
{
    if(_dataset != null)
    {
       _dataset.Dispose();
       _dataset = null;
    }
    //..More such member variables like oracle connection etc. _oraConnection
 }
Munish Goyal
sumber
Terbaik untuk hanya melacak ini dengan bendera yang terpisah mungkin.
Thulani Chivandikwa
3

jenis "tidak perlu mengatur objek menjadi nol setelah digunakan" tidak sepenuhnya akurat. Ada saatnya Anda perlu NULL variabel setelah membuangnya.

Ya, Anda harus SELALU menelepon .Dispose()atau .Close()pada apa pun yang memilikinya ketika Anda selesai. Baik itu file handle, koneksi basis data atau objek sekali pakai.

Terpisah dari itu adalah pola LazyLoad yang sangat praktis.

Katakanlah saya punya dan instantiated ObjAof class A. Class Amemiliki properti publik disebut PropBdari class B.

Secara internal, PropBmenggunakan variabel pribadi _Bdan default ke nol. Ketika PropB.Get()digunakan, memeriksa untuk melihat apakah _PropBbatal jika, membuka sumber daya yang dibutuhkan untuk instantiate Bke _PropB. Kemudian kembali _PropB.

Bagi pengalaman saya, ini adalah trik yang sangat berguna.

Dimana kebutuhan untuk null masuk adalah jika Anda mengatur ulang atau mengubah A dalam beberapa cara bahwa isi _PropBadalah anak dari nilai-nilai sebelumnya A, Anda harus Buang DAN null keluar _PropBsehingga LazyLoad dapat mengatur ulang untuk mengambil nilai yang tepat JIKA kode membutuhkannya.

Jika Anda hanya melakukan _PropB.Dispose()dan tak lama setelah mengharapkan pemeriksaan nol untuk LazyLoad berhasil, itu tidak akan nol, dan Anda akan melihat data basi. Akibatnya, Anda harus membatalkannya Dispose()hanya untuk memastikan.

Saya yakin berharap itu sebaliknya, tapi saya punya kode sekarang menunjukkan perilaku ini setelah Dispose()pada _PropBdan di luar fungsi panggilan yang melakukan Buang (dan dengan demikian hampir di luar ruang lingkup), alat peraga pribadi masih tidak nol, dan data basi masih ada.

Pada akhirnya, properti yang dibuang akan dibatalkan, tetapi itu tidak menentukan dari sudut pandang saya.

Alasan intinya, seperti yang disinggung dbkk adalah bahwa wadah induk ( ObjAwith PropB) menjaga instance _PropBdalam ruang lingkup, meskipun Dispose().

KenF
sumber
Contoh yang bagus menunjukkan bagaimana pengaturan ke null secara manual berarti kesalahan yang lebih fatal bagi penelepon yang merupakan hal yang baik.
bergulung
1

Ada beberapa kasus di mana masuk akal untuk membatalkan referensi. Misalnya, ketika Anda sedang menulis koleksi - seperti antrian prioritas - dan dengan kontrak Anda, Anda tidak boleh membiarkan benda-benda itu hidup untuk klien setelah klien menghapusnya dari antrian.

Tapi hal semacam ini hanya penting dalam koleksi berumur panjang. Jika antrian tidak akan bertahan pada akhir fungsi yang dibuatnya, maka itu jauh lebih penting.

Secara keseluruhan, Anda benar-benar tidak perlu repot. Biarkan kompiler dan GC melakukan pekerjaan mereka sehingga Anda dapat melakukan pekerjaan Anda.

Patrick
sumber
1

Lihatlah artikel ini juga: http://www.codeproject.com/KB/cs/idisposable.aspx

Sebagian besar, mengatur objek ke null tidak berpengaruh. Satu-satunya waktu Anda harus yakin untuk melakukannya adalah jika Anda bekerja dengan "objek besar", yang berukuran lebih besar dari 84K (seperti bitmap).

Scott Dorman
sumber
1

Stephen Cleary menjelaskan dengan sangat baik dalam posting ini: Haruskah Saya Mengatur Variabel ke Null untuk Membantu Pengumpulan Sampah?

Kata:

Jawaban Singkatnya, untuk yang Tidak Sabar Ya, jika variabelnya adalah bidang statis, atau jika Anda menulis metode enumerable (menggunakan pengembalian hasil) atau metode asinkron (menggunakan async dan menunggu). Kalau tidak, tidak.

Ini berarti bahwa dalam metode reguler (non-enumerable dan non-asynchronous), Anda tidak menetapkan variabel lokal, parameter metode, atau bidang instance ke nol.

(Bahkan jika Anda menerapkan IDisposable.Dispose, Anda tetap tidak harus menetapkan variabel ke nol).

Hal penting yang harus kita pertimbangkan adalah Static Fields .

Bidang statis selalu menjadi objek root , sehingga selalu dianggap "hidup" oleh pemulung. Jika bidang statis referensi objek yang tidak lagi diperlukan, itu harus diatur ke nol sehingga pengumpul sampah akan memperlakukannya sebagai memenuhi syarat untuk pengumpulan.

Mengatur bidang statis ke nol tidak ada artinya jika seluruh proses dimatikan. Seluruh tumpukan akan menjadi sampah yang dikumpulkan pada saat itu, termasuk semua objek root.

Kesimpulan:

Bidang statis ; hanya itu saja. Yang lainnya adalah buang - buang waktu .

Ebleme
sumber
0

Saya percaya dengan desain dari implementor GC, Anda tidak dapat mempercepat GC dengan pembatalan. Saya yakin mereka lebih suka Anda tidak khawatir dengan bagaimana / kapan GC berjalan - perlakukan seperti ini di mana-mana Menjadi melindungi dan mengawasi dan keluar untuk Anda ... (busur menunduk, mengangkat kepalan ke langit) .. .

Secara pribadi, saya sering secara eksplisit mengatur variabel ke nol ketika saya selesai dengan mereka sebagai bentuk dokumentasi diri. Saya tidak mendeklarasikan, menggunakan, lalu mengatur untuk membatalkan nanti - Saya membatalkan segera setelah itu tidak diperlukan lagi. Aku berkata, secara eksplisit, "Aku secara resmi selesai denganmu ... pergi ..."

Apakah pembatalan diperlukan dalam bahasa GC'd? Tidak. Apakah ini bermanfaat untuk GC? Mungkin ya, mungkin tidak, tidak tahu pasti, dengan desain saya benar-benar tidak bisa mengendalikannya, dan terlepas dari jawaban hari ini dengan versi ini atau itu, implementasi GC di masa depan dapat mengubah jawaban di luar kendali saya. Plus jika / ketika nulling dioptimalkan keluar itu sedikit lebih dari komentar mewah jika Anda mau.

Saya membayangkan jika itu membuat niat saya menjadi lebih jelas bagi orang bodoh miskin berikutnya yang mengikuti jejak saya, dan jika itu "mungkin" berpotensi membantu GC kadang-kadang, maka itu sangat berharga bagi saya. Kebanyakan itu membuat saya merasa rapi dan jelas, dan Mongo suka merasa rapi dan jelas. :)

Saya melihatnya seperti ini: Bahasa pemrograman ada untuk memungkinkan orang memberi ide kepada orang lain dan kompiler permintaan pekerjaan tentang apa yang harus dilakukan - kompiler mengonversi permintaan itu menjadi bahasa yang berbeda (kadang-kadang beberapa) untuk CPU - CPU dapat memberikan hoot bahasa apa yang Anda gunakan, pengaturan tab Anda, komentar, penekanan gaya, nama variabel, dll. - CPU adalah semua tentang bit stream yang memberi tahu apa yang register dan opcode dan lokasi memori untuk bermain-main. Banyak hal yang ditulis dalam kode tidak dikonversi menjadi apa yang dikonsumsi oleh CPU dalam urutan yang kami tentukan. C, C ++, C #, Lisp, Babel, assembler atau apa pun kita adalah teori daripada kenyataan, ditulis sebagai pernyataan kerja. Apa yang Anda lihat bukan apa yang Anda dapatkan, ya, bahkan dalam bahasa assembler.

Saya mengerti pola pikir "hal-hal yang tidak perlu" (seperti garis kosong) "tidak lain hanyalah derau dan kekacauan kode." Itu adalah awal karier saya; Saya benar-benar mengerti. Pada titik ini saya condong ke arah yang membuat kode lebih jelas. Ini tidak seperti saya menambahkan bahkan 50 baris "noise" ke program saya - itu beberapa baris di sini atau di sana.

Ada pengecualian untuk aturan apa pun. Dalam skenario dengan memori yang mudah menguap, memori statis, kondisi balapan, lajang, penggunaan data "basi" dan semua jenis busuk itu, itu berbeda: Anda PERLU mengelola memori Anda sendiri, mengunci dan membatalkan sebagai apropos karena memori bukan bagian dari GC'd Universe - semoga semua orang mengerti itu. Sisa waktu dengan bahasa GC'd itu masalah gaya daripada kebutuhan atau peningkatan kinerja yang dijamin.

Pada akhirnya, pastikan Anda memahami apa yang memenuhi syarat untuk GC dan apa yang tidak; mengunci, membuang, dan membatalkan dengan tepat; lilin menyala, lilin mati; bernafas, bernafas; dan untuk semua yang lain saya katakan: Jika rasanya enak, lakukanlah. Jarak tempuh Anda mungkin bervariasi ... sebagaimana mestinya ...

John
sumber
0

Saya pikir pengaturan sesuatu kembali ke nol berantakan. Bayangkan sebuah skenario di mana item yang diatur untuk sekarang diekspos katakanlah melalui properti. Sekarang entah bagaimana beberapa kode secara tidak sengaja menggunakan properti ini setelah item dibuang, Anda akan mendapatkan pengecualian referensi nol yang memerlukan beberapa penyelidikan untuk mencari tahu apa yang sebenarnya terjadi.

Saya percaya kerangka kerja sekali pakai akan memungkinkan membuang ObjectDisposedException yang lebih bermakna. Tidak mengatur ini kembali ke nol akan lebih baik daripada itu untuk alasan itu.

Thulani Chivandikwa
sumber
-1

Beberapa objek mengira .dispose()metode yang memaksa sumber daya untuk dihapus dari memori.

GateKiller
sumber
11
Tidak, tidak; Buang () tidak mengumpulkan objek - digunakan untuk melakukan pembersihan deterministik, biasanya melepaskan sumber daya yang tidak dikelola.
Marc Gravell
1
Mengingat bahwa determinisme hanya berlaku untuk sumber daya yang dikelola, bukan sumber daya yang tidak dikelola (yaitu memori)
nicodemus13