C # 'da IDisposable kullanmak ile bir yıkıcı kullanmak arasındaki fark nedir?


101

IDispose'u bir yıkıcı yerine bir sınıf üzerinde ne zaman uygularım? Bu makaleyi okudum ama yine de asıl noktayı kaçırıyorum.

Benim varsayımım, bir nesneye IDispose uygularsam, çöp toplayıcının bunu yapmasını beklemek yerine onu açıkça "yok edebilirim". Bu doğru mu?

Bu, bir nesnede her zaman açıkça Dispose çağırmam gerektiği anlamına mı geliyor? Bunun bazı yaygın örnekleri nelerdir?


5
Aslında, her Disposable nesnede Dispose çağırmalısınız. Yapıyı kullanarak bunu kolayca yapabilirsiniz using.
Luc Touraille

Ah, bu mantıklı. Dosya akışları için 'using' ifadesinin neden kullanıldığını hep merak etmiştim. Nesnenin kapsamıyla bir ilgisi olduğunu biliyorum, ancak bunu IDisposable arayüzle bağlamına koymadım.
Jordan Parmer

5
Unutulmaması gereken önemli bir nokta, sonlandırıcının bir sınıfın yönetilen üyelerine asla erişmemesi gerektiğidir , çünkü bu üyeler artık geçerli referanslar olmayabilirler.
Dan Bryant

Yanıtlar:


126

Bir sonlandırıcı (aka yıkıcı), çöp toplamanın (GC) bir parçasıdır - bu, ne zaman (veya olsa bile) belirsizdir, çünkü GC esas olarak bellek baskısının bir sonucu olarak (yani daha fazla alana ihtiyaç duyar). Yönetilen kaynakların kendi toplama / bertarafı olacağından, sonlandırıcılar genellikle yalnızca yönetilmeyen kaynakları temizlemek için kullanılır .

Bu nedenle IDisposable, nesneleri belirleyici olarak temizlemek için kullanılır , yani şimdi. Nesnenin belleğini (hala GC'ye ait olan) toplamaz - ancak örneğin dosyaları, veritabanı bağlantılarını vb. Kapatmak için kullanılır.

Bununla ilgili daha önceki birçok konu var:

Son olarak, bir IDisposablenesnenin ayrıca bir sonlandırıcıya sahip olmasının nadir olmadığını unutmayın ; bu durumda, Dispose()genellikle çağırır GC.SuppressFinalize(this), yani GC sonlandırıcıyı çalıştırmaz - sadece belleği atar (çok daha ucuz). Dispose()Nesneyi unutursanız, sonlandırıcı hala çalışır .


Teşekkürler! Bu çok mantıklı. Harika yanıtı çok takdir ediyorum.
Jordan Parmer

27
Söylenecek fazladan bir şey. Gerçekten ihtiyacınız olmadıkça sınıfınıza bir sonlandırıcı eklemeyin. Bir sonlandırıcı (yıkıcı) eklerseniz, GC onu çağırmalıdır (boş bir sonlandırıcı bile olsa) ve onu çağırmak için nesne her zaman bir gen 1 çöp toplama işleminden kurtulacaktır. Bu, GC'yi engelleyecek ve yavaşlatacaktır. Marc, SuppressFinalize'ı yukarıdaki kodda aramasını söylediği şey bu
Kevin Jones

1
Yani Finalize, yönetilmeyen kaynakları serbest bırakmaktır. Ancak Dispose, yönetilen ve yönetilmeyen kaynakları serbest bırakmak için kullanılabilir mi?
Dark_Knight

2
@Koyu evet; çünkü yönetim zincirindeki 6 seviye, hızlı temizlik gerektiren yönetilmeyen bir seviye olabilir
Marc Gravell

1
@KevinJones Bir sonlandırıcıya sahip nesnelerin gen 0'da hayatta kalması garantilidir, 1 değil, değil mi? Bunu .NET Performance adlı bir kitapta okudum.
David Klempfner

25

Finalize()Yöntemin rolü, bir .NET nesnesinin çöp toplandığında yönetilmeyen kaynakları temizleyebilmesini sağlamaktır . Ancak, veri tabanı bağlantıları veya dosya işleyicileri gibi nesneler, çöp toplamaya dayalı olarak mümkün olan en kısa sürede serbest bırakılmalıdır. Bunun için IDisposablearayüzü uygulamalı ve kaynaklarınızı Dispose()yöntemde serbest bırakmalısınız .


9

MSDN'de çok iyi bir açıklama var :

Bu arabirimin birincil kullanımı, yönetilmeyen kaynakları serbest bırakmaktır . Çöp toplayıcı , bu nesne artık kullanılmadığında, yönetilen bir nesneye ayrılan belleği otomatik olarak serbest bırakır . Ancak, çöp toplamanın ne zaman gerçekleşeceğini tahmin etmek mümkün değildir . Ayrıca, çöp toplayıcının pencere tutamaçları veya açık dosyalar ve akışlar gibi yönetilmeyen kaynaklar hakkında bilgisi yoktur .

Yönetilmeyen kaynakları çöp toplayıcıyla birlikte açıkça serbest bırakmak için bu arabirimin Dispose yöntemini kullanın . Bir nesnenin, tüketici nesnesi artık gerektiğinde bu yöntemi çağırabilir.


1
Bu tanımın en büyük zayıflıklarından biri, MS'nin yönetilmeyen kaynaklara örnekler vermesidir, ancak gördüğüm kadarıyla bu terimi aslında hiçbir zaman tanımlamaz. Yönetilen nesneler genellikle yalnızca yönetilen kod içinde kullanılabildiğinden, yönetilmeyen kodda kullanılan şeylerin yönetilmeyen kaynaklar olduğu düşünülebilir, ancak bu gerçekten doğru değildir. Yönetilmeyen kodların çoğu herhangi bir kaynak kullanmaz ve olaylar gibi bazı yönetilmeyen kaynaklar yalnızca yönetilen kod evreninde bulunur.
supercat

1
Kısa ömürlü bir nesne, uzun ömürlü bir nesneden gelen bir olaya abone olursa (örneğin, kısa ömürlü nesnenin yaşam süresi içinde meydana gelen herhangi bir değişikliğin bildirilmesini isterse), bu tür bir olay, yönetilmeyen bir kaynak olarak değerlendirilmelidir çünkü başarısızlık abonelikten çıkma olayı, kısa ömürlü nesnenin ömrünün uzun ömürlü nesnenin ömrüne uzatılmasına neden olur. Binlerce veya milyonlarca kısa ömürlü nesne bir olaya abone olduysa ancak abonelikten çıkmadan terk edildiyse, bu bir bellek veya CPU sızıntısına neden olabilir (çünkü her aboneliği işlemek için gereken süre artacaktır).
supercat

1
Yönetilen kod içindeki yönetilmeyen kaynakları içeren başka bir senaryo, havuzlardan nesne tahsisi olacaktır. Özellikle kodun .NET Micro Framework'te çalıştırılması gerekiyorsa (çöp toplayıcısı masaüstü makinelerdekinden çok daha az verimli), kodun örneğin her biri "kullanılmış" olarak işaretlenebilen bir dizi yapıya sahip olması yararlı olabilir. veya "ücretsiz". Bir tahsis talebi, halihazırda "serbest" olarak işaretlenmiş bir yapı bulmalı, onu "kullanılmış" olarak işaretlemeli ve ona bir indeks döndürmelidir; bir sürüm talebi bir yapıyı "özgür" olarak işaretlemelidir. Bir ayırma isteği, örneğin 23 döndürürse, o zaman ...
süper otomobil

1
... kod dizinin sahibine artık öğe # 23'e ihtiyaç duymadığını bildirmezse, bu dizi yuvası hiçbir zaman başka bir kod tarafından kullanılamayacaktır. Dizi yuvalarından bu tür manuel tahsis, GC oldukça verimli olduğu için masaüstü kodunda çok sık kullanılmaz, ancak Micro Framework üzerinde çalışan kodda büyük bir fark yaratabilir.
supercat


4

Her zaman aramanız gerekip gerekmediğine ilişkin sorunuz Disposegenellikle hararetli bir tartışmadır. .NET topluluğundaki saygın kişilerden ilginç bir bakış açısı için bu bloga bakın .

Kişisel olarak, Jeffrey Richter'in aramanın Disposezorunlu olmadığı şeklindeki pozisyonunun inanılmaz derecede zayıf olduğunu düşünüyorum. Fikrini haklı çıkarmak için iki örnek veriyor.

İlk örnekte Dispose, Windows Forms denetimlerini çağırmanın sıkıcı ve genel senaryolarda gereksiz olduğunu söylüyor . Ancak, Disposebu ana senaryolarda aslında kontrol kapsayıcıları tarafından otomatik olarak çağrıldığından bahsetmiyor .

İkinci örnekte, bir geliştiricinin IAsyncResult.WaitHandle, mülkün bekleme işleyicisini tembel bir şekilde başlatarak gereksiz bir performans cezasına yol açtığını fark etmeden , örneğinin agresif bir şekilde atılması gerektiğini yanlış bir şekilde varsayabileceğini belirtir . Ancak, bu örnekteki sorun, IAsyncResultkendisinin Microsoft'un IDisposablenesnelerle ilgilenmek için kendi yayınlanmış yönergelerine uymamasıdır. Yani, bir sınıf bir IDisposabletüre bir başvuru içeriyorsa, o zaman sınıfın kendisi uygulaması gerekir IDisposable. Bu IAsyncResultkurala uyulursa, kendi Disposeyöntemi, kurucu üyelerinden hangisinin elden çıkarılması gerektiğine karar verebilir.

Bu nedenle, birisinin daha zorlayıcı bir argümanı olmadıkça, çoğunlukla kötü tasarım seçimlerinden kaynaklanan bazı uç durumlar olacağı anlayışıyla "her zaman Dispose" kampında kalacağım.


3

Gerçekten oldukça basit. Cevaplandığını biliyorum ama tekrar deneyeceğim ama olabildiğince basit tutmaya çalışacağım.

Bir yıkıcı genellikle asla kullanılmamalıdır. Sadece çalıştırılır .net çalışmasını ister. Yalnızca çöp toplama döngüsünden sonra çalışacaktır. Uygulamanızın yaşam döngüsü boyunca aslında asla çalıştırılmayabilir. Bu nedenle, çalıştırılması 'gereken' bir yıkıcıya asla herhangi bir kod koymamalısınız. Ayrıca, sınıf içinde çalıştığı zaman var olan herhangi bir nesneye güvenemezsiniz (yıkıcıların çalıştığı sıra garanti edilmediğinden, bunlar zaten temizlenmiş olabilir).

IDisposible, temizlenmesi gereken kaynakları (yani dosya ve grafik tutamaçları) oluşturan bir nesneye sahip olduğunuzda kullanılmalıdır. Aslında, birçok kişi, bir yıkıcıya koyduğunuz her şeyin yukarıda listelenen nedenlerden dolayı IDisposable olması gerektiğini savunuyor.

Çoğu sınıf, sonlandırıcı yürütüldüğünde dispose çağırır, ancak bu yalnızca güvenli bir koruma olarak vardır ve asla güvenilmemelidir. İşiniz bittiğinde IDisposable uygulayan her şeyi açıkça imha etmelisiniz. IDisposable uygularsanız, finalizer'da dispose çağırmalısınız. Örnek için http://msdn.microsoft.com/en-us/library/system.idisposable.aspx adresine bakın .


Hayır, çöp toplayıcı asla Dispose () 'u çağırmaz. Bu sadece finalizer çağırır.
Marc Gravell

Bunu düzelttim. Sınıfların sonlandırıcıda dispose çağırması gerekiyor, ancak buna gerek yok.
DaEagle

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.