Bir nesneyi null vs Dispose () olarak ayarlama


108

CLR ve GC'nin çalışma şeklinden büyüleniyorum (CLR'yi C #, Jon Skeet'in kitapları / gönderileri ve daha fazlası aracılığıyla okuyarak bu konudaki bilgilerimi genişletmeye çalışıyorum).

Her neyse, şunu söylemek arasındaki fark nedir:

MyClass myclass = new MyClass();
myclass = null;

Veya MyClass'ı IDisposable ve bir yıkıcı uygulayarak ve Dispose () çağırarak?

Ayrıca, bir using deyimine sahip bir kod bloğum varsa (örn. Aşağıda), eğer kodda adım atarsam ve using bloğundan çıkarsam, nesne o zaman atılır mı yoksa bir çöp toplama meydana geldiğinde mi? Yine de using bloğunda Dispose () çağırırsam ne olur?

using (MyDisposableObj mydispobj = new MyDisposableObj())
{

}

Akış sınıfları (örn. BinaryWriter) Finalize yöntemi var mı? Neden bunu kullanmak isteyeyim?

Yanıtlar:


210

Bertarafı çöp toplamadan ayırmak önemlidir. Tamamen ayrı şeylerdir, bir dakika içinde geleceğim ortak bir nokta ile.

Disposeçöp toplama ve sonlandırma

Bir usingifade yazdığınızda , bu sadece bir dene / nihayet bloğu için sözdizimsel şekerdir, böylece ifadenin Disposegövdesindeki kod usingbir istisna atsa bile çağrılır . O değil nesne blok sonunda toplanan çöp olduğu anlamına gelir.

Elden çıkarma, yönetilmeyen kaynaklarla (bellek dışı kaynaklar) ilgilidir. Bunlar, kullanıcı arabirimi tanıtıcıları, ağ bağlantıları, dosya tanıtıcıları vb. Olabilir. Bunlar sınırlı kaynaklardır, bu nedenle bunları mümkün olan en kısa sürede serbest bırakmak istersiniz. Sen uygulamalıdır IDisposablesenin tipin yönetilmeyen kaynak "sahibi olan" her ne zaman (genellikle bir üzerinden doğrudan ya IntPtr(örneğin aracılığı ile dolaylı olarak) ya da Streambir SqlConnectionvb.)

Çöp toplamanın kendisi yalnızca bellekle ilgilidir - küçük bir değişiklik ile. Çöp toplayıcı artık referans verilemeyen nesneleri bulabilir ve onları serbest bırakabilir. Yine de her zaman çöp aramıyor - yalnızca ihtiyacı olduğunu algıladığında (örneğin, yığının bir "neslinin" hafızası biterse).

Büküm, sonuçlandırmadır . Çöp toplayıcı, artık erişilemeyen, ancak bir sonlandırıcıya sahip olan nesnelerin bir listesini tutar ( ~Foo()biraz kafa karıştırıcı bir şekilde C # 'da olduğu gibi yazılır - bunlar C ++ yıkıcıları gibi değildir). Sonlandırıcıları bu nesneler üzerinde çalıştırır, sadece hafızaları boşaltılmadan önce fazladan temizleme yapmaları gerekmesi durumunda.

Sonlandırıcılar, türdeki kullanıcının düzenli bir şekilde atmayı unutması durumunda neredeyse her zaman kaynakları temizlemek için kullanılır. Bu nedenle, bir açarsanız FileStreamancak Disposeveya aramayı unutursanız Close, sonlandırıcı sonunda temeldeki dosya tanıtıcısını sizin için serbest bırakır. İyi yazılmış bir programda, bence sonuçlandırıcılar neredeyse hiç ateş etmemelidir.

Bir değişkeni şuna ayarlamak null

Bir değişkeni ayarlamakla ilgili küçük bir nokta null- çöp toplama uğruna bu neredeyse hiç gerekli değildir. Bazen bir üye değişkeni ise bunu yapmak isteyebilirsiniz, ancak deneyimlerime göre bir nesnenin "bir kısmına" artık ihtiyaç duyulmaması nadirdir. Yerel bir değişken olduğunda, JIT genellikle bir referansı bir daha ne zaman kullanmayacağınızı bilecek kadar akıllıdır (yayın modunda). Örneğin:

StringBuilder sb = new StringBuilder();
sb.Append("Foo");
string x = sb.ToString();

// The string and StringBuilder are already eligible
// for garbage collection here!
int y = 10;
DoSomething(y);

// These aren't helping at all!
x = null;
sb = null;

// Assume that x and sb aren't used here

Yerel bir değişkeni ayarlamanın faydalı olabileceği tek zaman null, bir döngüde olduğunuz zamandır ve döngünün bazı dallarının değişkeni kullanması gerekir, ancak bilmediğiniz bir noktaya ulaştığınızı bilirsiniz. Örneğin:

SomeObject foo = new SomeObject();

for (int i=0; i < 100000; i++)
{
    if (i == 5)
    {
        foo.DoSomething();
        // We're not going to need it again, but the JIT
        // wouldn't spot that
        foo = null;
    }
    else
    {
        // Some other code 
    }
}

IDisposable / sonlandırıcıları uygulama

Öyleyse, kendi türleriniz sonuçlandırıcıları uygulamalı mı? Neredeyse kesinlikle değil. Yalnızca dolaylı olarak yönetilmeyen kaynakları tutuyorsanız (örneğin FileStream, bir üye değişkeniniz varsa), kendi sonlandırıcınızı eklemenin bir faydası olmayacaktır: nesneniz olduğu zaman akış neredeyse kesinlikle çöp toplama için uygun olacaktır, bu nedenle yalnızca güvenebilirsiniz FileStreambir sonlandırıcıya sahip olmak (gerekirse - başka bir şeye atıfta bulunabilir, vb.). Yönetilmeyen bir kaynağı "neredeyse" doğrudan tutmak istiyorsanız SafeHandle, arkadaşınızdır - devam etmek biraz zaman alır, ancak bu, bir daha asla bir sonlandırıcı yazmaya neredeyse hiç gerek duymayacağınız anlamına gelir . Bir kaynak (an IntPtr) üzerinde gerçekten doğrudan bir kontrolünüz varsa, genellikle bir sonlandırıcıya ihtiyacınız vardır veSafeHandleyapabileceğin en kısa zamanda. (Orada iki bağlantı vardır - ideal olarak ikisini de okuyun.)

Joe Duffy'nin sonlandırıcılar ve IDisposable (birçok akıllı insanla birlikte yazılmıştır) hakkında okumaya değer çok uzun bir kılavuz seti vardır. Sınıflarınızı mühürlediğinizde, bunun hayatı çok daha kolay hale getireceğinin farkında olmak gerekir: DisposeYeni bir sanal Dispose(bool)yöntemi çağırmak için geçersiz kılma modeli , sadece sınıfınız kalıtım için tasarlandığında geçerlidir.

Bu biraz saçma oldu, ancak lütfen biraz istediğin yerde açıklama iste :)


"Yerel bir değişkeni null olarak ayarlamanın faydalı olabileceği tek sefer" - belki de bazı zorlu "yakalama" senaryoları (aynı değişkenin birden fazla yakalanması) - ancak gönderiyi karmaşıklaştırmaya değmeyebilir! +1 ...
Marc Gravell

@Marc: Bu doğru - Yakalanan değişkenleri düşünmemiştim bile. Hmm. Evet, sanırım bunu rahat bırakacağım;)
Jon Skeet

Lütfen yukarıdaki kod pasajınızda "foo = null" ayarladığınızda ne olacağını söyleyebilir misiniz? Bildiğim kadarıyla, bu satır yalnızca yönetilen öbekteki foo nesnesine işaret eden bir değişkenin değerini temizler? Öyleyse soru, oradaki foo nesnesine ne olacağıdır? buna elden çıkarma dememiz gerekmez mi?
odiseh

@odiseh: Eğer nesne tek kullanımlıksa, evet - onu atmalısınız. Cevabın bu bölümü sadece tamamen ayrı olan çöp toplama ile ilgiliydi.
Jon Skeet

1
Bazı atılabilir endişeler hakkında bir açıklama arıyordum, bu yüzden Google'da "IDisposable Skeet" aradım ve bunu buldum. Harika! : D
Maciej Wozniak

22

Bir nesneyi attığınızda kaynaklar serbest kalır. Bir değişkene null atadığınızda, sadece referansı değiştirmiş olursunuz.

myclass = null;

Bunu çalıştırdıktan sonra, sınıfımın bahsettiği nesne hala var ve GC onu temizlemeye gelene kadar devam edecek. Dispose açıkça çağrılırsa veya bir kullanım bloğundaysa, tüm kaynaklar mümkün olan en kısa sürede serbest bırakılacaktır.


7
O olabilir hala o çizgiyi yürüttükten sonra yok - bu atık olarak toplanmış olabilir önce bu hat. JIT akıllıdır - bunun gibi satırları neredeyse her zaman alakasız hale getirir.
Jon Skeet

6
Null olarak ayarlamak, nesne tarafından tutulan kaynakların asla serbest bırakılmadığı anlamına gelebilir . GC elden çıkarmaz, yalnızca sonlandırır, bu nedenle nesne doğrudan yönetilmeyen kaynakları tutarsa ​​ve sonlandırıcısı atmazsa (veya bir sonlandırıcıya sahip değilse) bu kaynaklar sızar. Dikkat edilmesi gereken bir şey.
LukeH

6

İki operasyonun birbiriyle pek ilgisi yok. Bir referansı null olarak ayarladığınızda, bunu basitçe yapar. Referans verilen sınıfı kendi başına etkilemez. Değişkeniniz artık eskiden olduğu nesneyi işaret etmez, ancak nesnenin kendisi değişmez.

Dispose () 'u çağırdığınızda, bu nesnenin kendisinde bir yöntem çağrısıdır. Dispose yöntemi ne yaparsa yapsın artık nesne üzerinde yapılıyor. Ancak bu, nesneye referansınızı etkilemez.

Tek örtüşen olmasıdır zaman bir nesneye artık referanslar vardır, bu olacak sonunda çöp toplanan olsun. Ve sınıf, IDisposable arabirimini uygularsa, çöp toplanmadan önce nesnede Dispose () çağrılır.

Ancak, referansınızı null olarak ayarladıktan hemen sonra, iki nedenden ötürü bu gerçekleşmeyecektir. Birincisi, başka referanslar mevcut olabilir, bu nedenle henüz çöp toplanmayacaktır ve ikincisi, bu son referans olsa bile artık çöp toplanmaya hazırdır, çöp toplayıcı silmeye karar verene kadar hiçbir şey olmayacaktır. nesne.

Dispose () işlevini bir nesnede çağırmak, nesneyi hiçbir şekilde "öldürmez". Genellikle nesnenin daha sonra güvenli bir şekilde silinebilmesi için temizlemek için kullanılır , ancak nihayetinde Dispose hakkında büyülü bir şey yoktur, bu sadece bir sınıf yöntemi.


Sanırım bu yanıt "özyinelemeli" yanıtına iltifat ediyor veya bir ayrıntı.
dance2die
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.