IDisposable arayüzünün doğru kullanımı


1657

Microsoft belgelerini okurken ,IDisposable arabirimin "birincil" kullanımının yönetilmeyen kaynakları temizlemek olduğunu biliyorum .

Bana göre, "yönetilmeyen" veritabanı bağlantıları, soketler, pencere kolları, vb. Gibi şeyler anlamına gelir. Ancak, çöp toplayıcısının ilgilenmesi gerektiğinden, bana gereksiz olan Dispose()ücretsiz yönetilen kaynaklara yöntemin uygulandığı kodu gördüm . bu senin için.

Örneğin:

public class MyCollection : IDisposable
{
    private List<String> _theList = new List<String>();
    private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();

    // Die, clear it up! (free unmanaged resources)
    public void Dispose()
    {
        _theList.clear();
        _theDict.clear();
        _theList = null;
        _theDict = null;
    }

Sorum şu: Bu, çöp toplayıcısının boş belleğini MyCollectionnormalde kullandığından daha hızlı yapıyor mu?

edit : Şimdiye kadar insanlar veritabanı bağlantıları ve bitmapler gibi yönetilmeyen kaynakları temizlemek için IDisposable kullanarak bazı iyi örnekler gönderdi. Ancak _theList, yukarıdaki kodda bir milyon dizgi içerdiğini ve çöp toplayıcıyı beklemek yerine şimdi bu belleği boşaltmak istediğinizi varsayalım . Yukarıdaki kod bunu başarabilir mi?


34
Kabul edilen cevabı seviyorum çünkü size IDisposable'ı kullanmanın doğru 'desenini' söylüyor, ancak OP'nin düzenlemesinde söylediği gibi, amaçlanan soruyu cevaplamıyor. IDisposable, GC'yi 'çağırmaz', sadece bir nesneyi yok edilebilir olarak işaretler. Ancak GC'nin devreye girmesini beklemek yerine, 'şu anda' hafızayı boşaltmanın gerçek yolu nedir? Bence bu soru daha fazla tartışmayı hak ediyor.
Punit Vora

40
IDisposablehiçbir şey işaretlemez. DisposeYöntem örneği tarafından kullanılan kaynakları temizlemek için yapması gereken şeyi yapıyor. Bunun GC ile bir ilgisi yok.
John Saunders

4
@John. Anlıyorum IDisposable. Bu yüzden kabul edilen cevabın, OP'nin IDiposable'ın <i> hafızayı boşaltmaya </i> yardımcı olup olmayacağı ile ilgili sorusuna (ve takip düzenlemesine) cevap vermediğini söyledim. Yana IDisposableDediğin sonra gibi, bellek, yalnızca kaynakları serbest ile hiçbir ilgisi yoktur, OP onun örnekte ne yaptığını olan hiç boş başardı başvuruları ayarlamak için gerek yoktur. Bu nedenle, sorusuna doğru cevap "Hayır, belleği boşaltmaya daha hızlı yardımcı olmaz. Aslında, belleği boşaltmaya hiç yardımcı olmaz, sadece kaynaklar" dır. Neyse, girişiniz için teşekkürler.
Punit Vora

9
@desigeek: eğer durum buysa, o zaman "IDisposable GC'yi 'çağırmaz' dememelisiniz, sadece bir nesneyi yok edilebilir olarak işaretler"
John Saunders

5
@desigeek: Belleği kararlı bir şekilde boşaltmanın garantili bir yolu yoktur. GC.Collect () öğesini çağırabilirsiniz, ancak bu kibar bir istek, bir talep değil. Çöp toplama işleminin devam etmesi için çalışan tüm iş parçacıkları askıya alınmalıdır - daha fazla bilgi edinmek istiyorsanız .NET güvenlik noktaları kavramını okuyun, örn. Msdn.microsoft.com/en-us/library/678ysw69(v=vs.110). aspx . Bir iş parçacığı askıya alınamazsa, örneğin yönetilmeyen koda bir çağrı olduğu için GC.Collect () hiçbir şey yapamaz.
Beton Gannet

Yanıtlar:


2608

İmhası noktası olan yönetilmeyen kaynakları serbest bırakmak. Bir noktada yapılması gerekir, aksi takdirde asla temizlenmezler. Çöp toplayıcı bilmiyor nasıl çağırmak için DeleteHandle()türünde bir değişkeni IntPtr, bilmez olmadığını çağırması gerektiğine ya da değil DeleteHandle().

Not : Yönetilmeyen kaynak nedir? Microsoft .NET Framework'te bulduysanız: yönetilir. Eğer MSDN'yi kendiniz alaysanız, yönetilmez. .NET Framework'te kullanabileceğiniz her şeyin güzel ve rahat dünyasının dışına çıkmak için P / Invoke çağrılarını kullandığınız her şey yönetilmez - ve artık onu temizlemekle sorumlusunuz.

Oluşturduğunuz nesnenin, yönetilmeyen kaynakları temizlemek için dış dünyanın arayabileceği bazı yöntemleri ortaya çıkarması gerekir . Yöntem istediğiniz gibi adlandırılabilir:

public void Cleanup()

veya

public void Shutdown()

Ancak bunun yerine bu yöntem için standart bir ad vardır:

public void Dispose()

Oluşturulan bir arayüz bile vardı IDisposable, bu sadece bir yönteme sahip:

public interface IDisposable
{
   void Dispose()
}

Böylece nesnenizin IDisposablearayüzü ortaya çıkarmasını sağlarsınız ve bu şekilde yönetilmeyen kaynaklarınızı temizlemek için bu tek yöntemi yazdığınıza söz verirsiniz:

public void Dispose()
{
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}

Ve işiniz bitti. Dışında daha iyisini yapabilirsin.


Nesneniz bir tür çerçeve arabelleği olarak 250MB System.Drawing.Bitmap (yani .NET tarafından yönetilen Bitmap sınıfı) ayırdıysa ne olur ? Elbette, bu yönetilen bir .NET nesnesidir ve çöp toplayıcı bunu serbest bırakır. Ama gerçekten orada otururken 250MB bellek bırakmak istiyor musunuz - çöp toplayıcının sonunda gelip serbest kalmasını bekliyor musunuz? Açık bir veritabanı bağlantısı varsa ne olur ? Şüphesiz bu bağlantının açık olmasını ve GC'nin nesneyi tamamlamasını beklemesini istemiyoruz.

Kullanıcı Dispose()(artık nesneyi kullanmayı planlamıyor demektir) çağırdıysa, neden bu savurgan bitmapler ve veritabanı bağlantılarından kurtulmuyorsunuz?

Şimdi yapacağız:

  • yönetilmeyen kaynaklardan kurtulmak (çünkü yapmak zorundayız) ve
  • yönetilen kaynaklardan kurtulun (çünkü yardımcı olmak istiyoruz)

Bu Dispose()yönetilen nesnelerden kurtulmak için yöntemimizi güncelleyelim :

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose();
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose();
      this.frameBufferImage = null;
   }
}

Ve her şey iyi, sadece daha iyisini yapabilirsin !


Kişi nesnenizi çağırmayı unutursa ne olur Dispose()? O zaman yönetilmeyen bazı kaynakları sızdırırlardı !

Not: Yönetilen kaynakları sızdırmazlar , çünkü sonunda çöp toplayıcı bir arka plan iş parçacığında çalışır ve kullanılmayan nesnelerle ilişkili belleği boşaltır. Bu, nesnenizi ve kullandığınız yönetilen nesneleri (ör. BitmapVe DbConnection) içerir.

Kişi aramayı unuttuysa Dispose(), yine de pastırmasını kurtarabiliriz! Biz hala onu aramak için bir yol var için çöp toplayıcı nihayet kurtararak etrafında aldığında (yani sonlandırma) bizim nesne: Bunlardan.

Not: Çöp toplayıcı sonunda yönetilen tüm nesneleri serbest bırakır. Bunu yaptığında Finalize , nesne üzerindeki yöntemi çağırır . GC biliyorum, ya da yaklaşık bakımı, gelmez senin bertaraf yöntemine. Bu, yönetilmeyen şeylerden kurtulmak istediğimizde çağırdığımız bir yöntem için seçtiğimiz bir isimdi.

Nesnemizin Çöp toplayıcı tarafından imha edilmesi, bu sinir bozucu yönetilmeyen kaynakları boşaltmak için mükemmel bir zamandır. Bunu Finalize()yöntemi geçersiz kılarak yapıyoruz .

Not: C # 'da Finalize()yöntemi açıkça geçersiz kılmazsınız. Bir C ++ yıkıcıya benzeyen bir yöntem yazarsınız ve derleyici bu yöntemi sizin uygulamanız olarak alır :Finalize()

~MyObject()
{
    //we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
    Dispose(); //<--Warning: subtle bug! Keep reading!
}

Ama bu kodda bir hata var. Çöp toplayıcı bir arka plan iş parçacığında çalışır ; iki nesnenin yok olma sırasını bilmiyorsunuz. Dispose()Kodunuzda, kurtulmaya çalıştığınız yönetilen nesnenin (yardımcı olmak istediğiniz için) artık orada olmaması tamamen mümkündür :

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
      this.frameBufferImage = null;
   }
}

Peki lüzum bir yoludur Finalize()anlatmak için Dispose()o gerektiğini herhangi yönetilen dokunma (onlar çünkü kaynaklar olmayabilir artık) hala yönetilmeyen kaynakları serbest bırakarak ederken,.

Bunu yapmak için standart örüntü bir üçüncü (!) Yöntemine sahip olmak Finalize()ve Dispose()her ikisini de çağırmaktır ; Burada Boole ifadesini geçtiğinizde (aksine ) çağırırsınız , yani ücretsiz yönetilen kaynaklar güvenli.Dispose()Finalize()

Bu yöntem olabilir "CoreDispose" veya "MyInternalDispose" gibi bazı keyfi ad verilir, ama onu aramak için gelenektir edilebilir Dispose(Boolean):

protected void Dispose(Boolean disposing)

Ancak daha yararlı bir parametre adı şunlar olabilir:

protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too, but only if I'm being called from Dispose
   //(If I'm being called from Finalize then the objects might not exist
   //anymore
   if (itIsSafeToAlsoFreeManagedObjects)  
   {    
      if (this.databaseConnection != null)
      {
         this.databaseConnection.Dispose();
         this.databaseConnection = null;
      }
      if (this.frameBufferImage != null)
      {
         this.frameBufferImage.Dispose();
         this.frameBufferImage = null;
      }
   }
}

Ve IDisposable.Dispose()yöntemi uygulamanızı şu şekilde değiştirirsiniz :

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
}

ve sonlandırıcınız:

~MyObject()
{
   Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}

Not : Nesneniz uygulayan bir nesneden geliyorsa, Dispose'i geçersiz kıldığınızda temel Dispose yöntemini Disposeçağırmayı unutmayın.

public override void Dispose()
{
    try
    {
        Dispose(true); //true: safe to free managed resources
    }
    finally
    {
        base.Dispose();
    }
}

Ve her şey iyi, sadece daha iyisini yapabilirsin !


Kullanıcı Dispose()nesnenizi çağırırsa , her şey temizlendi. Daha sonra, çöp toplayıcı yanına gelip Sonlandır'ı çağırdığında, Disposetekrar arayacaktır .

Bu sadece israf etmekle kalmaz, aynı zamanda nesnenizin son çağrıdan Dispose()attığınız nesnelere önemsiz referansları varsa, bunları tekrar atmaya çalışacaksınız!

Kodumda, attığım nesnelere yapılan başvuruları kaldırmaya dikkat ettiğimi fark edeceksiniz, bu yüzden Disposeönemsiz bir nesne başvurusunu çağırmaya çalışmıyorum . Ama bu ince bir böceğin içeri sürülmesini engellemedi.

Kullanıcı çağırdığında Dispose(): CursorFileBitmapIconServiceHandle tanıtıcısı yok edilir. Daha sonra çöp toplayıcı çalıştığında, aynı sapı tekrar yok etmeye çalışacaktır.

protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy 
   ...
}

Bunu düzeltmenin yolu, çöp toplayıcıya nesneyi sonlandırmak için uğraşmaya gerek olmadığını söyler - kaynakları zaten temizlendi ve daha fazla çalışmaya gerek yok. Sen çağırarak bunu GC.SuppressFinalize()içinde Dispose()yöntemle:

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
   GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}

Şimdi kullanıcı aradı Dispose(), biz var:

  • serbest yönetilmeyen kaynaklar
  • serbest yönetilen kaynaklar

GC'de sonlandırıcıyı çalıştırmanın bir anlamı yoktur - her şey halledilir.

Finalize'i yönetilmeyen kaynakları temizlemek için kullanamaz mıydım?

İçin belgeler Object.Finalizediyor:

Sonlandırma yöntemi, nesne yok edilmeden önce geçerli nesne tarafından tutulan yönetilmeyen kaynaklarda temizleme işlemleri gerçekleştirmek için kullanılır.

Ancak MSDN belgeleri şunları da söylüyor IDisposable.Dispose:

Yönetilmeyen kaynakların serbest bırakılması, serbest bırakılması veya sıfırlanmasıyla ilişkili uygulama tanımlı görevleri gerçekleştirir.

Peki hangisi? Yönetilmeyen kaynakları temizlemem için hangisi benim için? Cevap:

Bu senin seçimin! Ama seçin Dispose.

Yönetilmeyen temizliğinizi kesinleştiriciye yerleştirebilirsiniz:

~MyObject()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //A C# destructor automatically calls the destructor of its base class.
}

Buradaki sorun, çöp toplayıcının nesnenizi ne zaman bitireceği konusunda hiçbir fikriniz yok. Yönetilmeyen, ihtiyaç duyulmayan, kullanılmayan yerel kaynaklarınız, çöp toplayıcı sonunda çalışana kadar değişecektir . Sonra sonlandırıcı yönteminizi çağırır; yönetilmeyen kaynakları temizleme. Object.Finalize dokümantasyonu şunları göstermektedir:

Sonlandırıcının yürütüldüğü kesin süre tanımsızdır. Sınıfınızın örnekleri için kaynakların kararlı bir şekilde serbest bırakılmasını sağlamak için, bir Kapat yöntemi uygulayın veya bir IDisposable.Disposeuygulama sağlayın .

Bu, Disposeyönetilmeyen kaynakları temizlemek için kullanmanın erdemidir ; yönetilmeyen kaynakların ne zaman temizlendiğini bilir ve kontrol edersiniz. Onların yok edilmesi "deterministik" tir .


Orijinal sorunuzu cevaplamak için: Neden GC bunu yapmaya karar verdiğinden ziyade hafızayı serbest bırakmıyorsunuz? Bunun yüz tanıma yazılımı ihtiyaçları iç görüntülerin 530 MB kurtulmak için şimdi artık gerekli olduğuna göre,. Yapmadığımız zaman: makine bir takas durağına öğütülür.

Bonus Okuma

Bu cevabın stilini (açıklayan seven herkes için neden bu kadar, ne kadar bariz hale gelir), sana Don Box'ın Essential COM Bölüm One okumak öneririz:

35 sayfada ikili nesneleri kullanmanın sorunlarını açıklar ve COM'u gözünüzün önünde icat eder. COM'un nedenini anladıktan sonra , kalan 300 sayfa açıktır ve Microsoft'un uygulamasını ayrıntılı olarak açıklar.

Bence herhangi bir nesne veya COM ile uğraşan her programcı en azından ilk bölümü okumalıdır. Bu şimdiye kadarki en iyi açıklamadır.

Ekstra Bonus Okuma

Bildiğiniz her şey Eric Lippert tarafından yanlış olduğunda

Bu nedenle doğru bir sonlandırıcı yazmak gerçekten çok zordur ve size verebileceğim en iyi tavsiye denememektir .


12
Daha iyisini yapabilirsiniz - Dispose içinde GC.SuppressFinalize () öğesine bir çağrı eklemeniz gerekir.
süpürgelik

55
@Daniel Earwicker: Doğru. Microsoft, Win32'yi tamamen kullanmayı bırakmanızı ve güzel çıkarılabilir, taşınabilir, cihazdan bağımsız .NET Framework çağrılarına bağlı kalmanızı ister. Altındaki işletim sistemi etrafında gezinmek istiyorsanız; çünkü işletim sisteminin ne yaptığını bildiğinizi düşünüyorsunuz : hayatınızı kendi ellerinize alıyorsunuz. Her .NET uygulaması Windows veya masaüstünde çalışmaz.
Ian Boyd

34
Bu harika bir cevap ama ben standart bir durumda ve sınıf zaten Dispose uygulayan bir taban sınıfından türediği bir durumda için son bir kod listesinden yarar olacağını düşünüyorum. örneğin burada ( msdn.microsoft.com/en-us/library/aa720161%28v=vs.71%29.aspx ) okuduğumdan , zaten Dispose ( hey ben yeniyim).
integra753

5
@GregS, ve diğerleri: Genellikle referanslar ayarlamak rahatsız olmaz null. Her şeyden önce, bu onları yapamayacağınız anlamına gelir readonlyve ikincisi, çok çirkin !=nullkontroller yapmanız gerekir (örnek kodda olduğu gibi). Bir bayrağınız olabilir disposed, ama rahatsız etmemek daha kolaydır. .NET GC, bir alana yapılan başvurunun x, x.Dispose()çizgiyi geçtiğinde artık 'kullanılmadığı' sayılmayacak kadar agresiftir .
porges

7
Don Box'ın bahsettiğiniz kitabın ikinci sayfasında, "ayrıntıları okuyucu için bir alıştırma olarak bırakılan" arama algoritmasının O (1) uygulaması örneğini kullanıyor. Güldüm.
wip

65

IDisposablegenellikle usingifadeyi kullanmak ve yönetilen nesnelerin deterministik temizliğini yapmak için kolay bir yoldan yararlanmak için kullanılır .

public class LoggingContext : IDisposable {
    public Finicky(string name) {
        Log.Write("Entering Log Context {0}", name);
        Log.Indent();
    }
    public void Dispose() {
        Log.Outdent();
    }

    public static void Main() {
        Log.Write("Some initial stuff.");
        try {
            using(new LoggingContext()) {
                Log.Write("Some stuff inside the context.");
                throw new Exception();
            }
        } catch {
            Log.Write("Man, that was a heavy exception caught from inside a child logging context!");
        } finally {
            Log.Write("Some final stuff.");
        }
    }
}

6
Bunu kişisel olarak seviyorum, ancak çerçeve tasarım yönergelerine uymuyor.
mqp

4
Özellikle istisna işleme, kilitleme ve yönetilmeyen kaynak kullanma blokları ile karmaşık yollarla karıştırıldığında kolay deterministik kapsamlar ve kapsam yapıları / temizlemeler sağladığından uygun tasarımı düşünürdüm. Dil bunu birinci sınıf bir özellik olarak sunuyor.
yfeldblum

FDG'de belirtilen kurallara tam olarak uymaz, ancak "using ifadesi" tarafından kullanılması için modelin kesinlikle geçerli bir kullanımıdır.
Scott Dorman

2
Log.Outdent atmadığı sürece, bununla ilgili kesinlikle yanlış bir şey yoktur.
Daniel Earwicker

1
Çeşitli yanıtlar İstisnai güvenlik için “kapsam dışı davranış” elde etmenin bir yolu olarak IDisposable ve “kullanma” nın kullanılması küfürlü mü? farklı insanların neden bu tekniği sevip sevmedikleri hakkında biraz daha ayrıntıya girelim. Biraz tartışmalı.
Brian

44

Dispose deseninin amacı, yönetilen ve yönetilmeyen kaynakları temizlemek için bir mekanizma sağlamaktır ve bu gerçekleştiğinde Dispose yönteminin nasıl çağrıldığına bağlıdır. Örneğinizde, Dispose kullanımı aslında dispose ile ilgili herhangi bir şey yapmamaktadır, çünkü bir listeyi temizlemenin bu tahsis edilen koleksiyon üzerinde bir etkisi yoktur. Benzer şekilde, değişkenleri null değerine ayarlama çağrılarının da GC üzerinde bir etkisi yoktur.

Atma düzeninin nasıl uygulanacağı hakkında daha fazla ayrıntı için bu makaleye göz atabilirsiniz , ancak temel olarak şöyle görünür:

public class SimpleCleanup : IDisposable
{
    // some fields that require cleanup
    private SafeHandle handle;
    private bool disposed = false; // to detect redundant calls

    public SimpleCleanup()
    {
        this.handle = /*...*/;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources.
                if (handle != null)
                {
                    handle.Dispose();
                }
            }

            // Dispose unmanaged managed resources.

            disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

Burada en önemli yöntem aslında iki farklı koşulda çalışan Dispose (bool) yöntemidir:

  • disposing == true: yöntem doğrudan veya dolaylı olarak bir kullanıcı kodu tarafından çağrıldı. Yönetilen ve yönetilmeyen kaynaklar atılabilir.
  • disposing == false: yöntem, sonlandırıcının içinden çalışma zamanı tarafından çağrıldı ve diğer nesnelere başvurmamalısınız. Yalnızca yönetilmeyen kaynaklar atılabilir.

GC'nin temizleme işlemini halletmesine izin vermenin sorunu, GC'nin bir toplama döngüsünü ne zaman çalıştıracağı (GC.Collect () diyebilirsiniz, ancak gerçekten yapmamalısınız) üzerinde gerçek bir kontrolünüz olmamasıdır. gerekenden daha uzun süre. Unutmayın, Dispose () öğesini çağırmak aslında bir toplama döngüsüne veya GC'nin nesneyi toplamasına / boşaltmasına neden olmaz; sadece kullanılan kaynakları daha kararlı bir şekilde temizlemek ve GC'ye bu temizlemenin zaten yapıldığını söylemek için araçlar sağlar.

Tek kullanımlık ve tasfiye kalıbının tüm noktası, belleği hemen boşaltmakla ilgili değildir. Dispose çağrısının aslında hafızayı hemen boşaltma şansı bile olsa, bertaraf == false senaryosunu ve yönetilmeyen kaynakları manipüle ettiği zamandır. Yönetilen kod için, GC gerçekten üzerinde hiçbir kontrole sahip olmadığınız bir toplama döngüsünü çalışana kadar bellek geri kazanılmayacaktır (daha önce bahsettiğim GC.Collect () öğesini çağırmak dışında, iyi bir fikir değildir).

Senaryonuz gerçekten geçerli değil çünkü .NET dizeleri herhangi bir zararsız kaynak kullanmıyor ve IDisposable uygulamaz, onları "temizlenmeye" zorlamanın bir yolu yoktur.


4
Sonlandırıcıyı uygulamayı unuttunuz mu?
Budda

@Budda: Hayır, SafeHandle kullanıyor. Yıkıcıya gerek yok.
Henk Holterman

9
Dispose () öğesine yapılan birden çok çağrı için güvenlik ağı eklemek üzere +1. Teknik özellik, birden fazla çağrının güvenli olması gerektiğini söylüyor. Çok fazla Microsoft sınıfı bunu uygulayamıyor ve can sıkıcı ObjectDisposedException alıyorsunuz.
Jesse Chisholm

5
Ancak Dispose (bool çöpe) SimpleCleanup sınıfınızda kendi yönteminizdir ve çerçeve tarafından asla çağrılmaz. Bunu parametre olarak yalnızca "true" ile çağırdığınız için, 'imha' asla yanlış olmaz. Kodunuz IDisposable için MSDN örneğine çok benziyor, ancak @Budda'nın işaret ettiği gibi, sonlandırıcıdan yoksun, ki bu da = false ile çağrının geleceği yerdir.
yoyo

19

Dispose çağrıldıktan sonra bir nesnenin yöntemlerine başka çağrı yapılmamalıdır (bir nesne daha fazla Dispose çağrısına tolerans göstermelidir). Dolayısıyla sorudaki örnek saçmadır. Dispose çağrılırsa, nesnenin kendisi atılabilir. Bu nedenle, kullanıcı tüm nesneye yapılan tüm referansları atmalı (null değerine ayarlamalıdır) ve içindeki tüm ilgili nesneler otomatik olarak temizlenecektir.

Yönetilen / yönetilmeyen hakkında genel soruya ve diğer cevaplardaki tartışmaya gelince, bu soruya verilen herhangi bir cevabın yönetilmeyen bir kaynak tanımı ile başlaması gerektiğini düşünüyorum.

Bunun kaygısı, sistemi bir duruma getirmek için çağırabileceğiniz bir fonksiyonun olması ve onu bu durumdan çıkarmak için çağırabileceğiniz başka bir fonksiyonun olmasıdır. Şimdi, tipik örnekte, birincisi bir dosya tanıtıcısı döndüren bir işlev olabilir ve ikincisi bir çağrı olabilir CloseHandle.

Ama - ve bu anahtar - bunlar eşleşen herhangi bir işlev çifti olabilir. Biri bir devlet kurar, diğeri onu yıkar. Devlet henüz inşa edilmiş ancak henüz parçalanmamışsa, kaynağın bir örneği vardır. Kaldırmanın doğru zamanda gerçekleşmesini ayarlamanız gerekir - kaynak CLR tarafından yönetilmez. Otomatik olarak yönetilen tek kaynak türü bellektir. İki tür vardır: GC ve yığın. Değer türleri yığın tarafından yönetilir (veya referans türleri içinde bir gezintiye atılır) ve referans türleri GC tarafından yönetilir.

Bu işlevler, serbestçe araya sokulabilen veya mükemmel şekilde yuvalanması gereken durum değişikliklerine neden olabilir. Durum değişiklikleri güvenli olabilir veya olmayabilir.

Adalet'in sorusundaki örneğe bakın. Günlük dosyasının girintisinde yapılan değişiklikler mükemmel şekilde iç içe yerleştirilmiş olmalı veya her şey yanlış gidiyor. Ayrıca iplik güvenli olması pek olası değildir.

Yönetilmeyen kaynaklarınızın temizlenmesini sağlamak için çöp toplayıcıyla gezintiye çıkmak mümkündür. Ancak, yalnızca durum değişikliği işlevleri iş parçacığı güvenliyse ve iki durumun herhangi bir şekilde örtüşen yaşamları olabilir. Bu yüzden Adalet'in bir kaynak örneğinde kesinleştirici OLMAMALIDIR! Kimseye yardım etmezdi.

Bu tür kaynaklar için, IDisposablekesinleştirici olmadan uygulayabilirsiniz . Sonlandırıcı kesinlikle isteğe bağlıdır - olması gerekir. Bu, birçok kitapta göz ardı edilir veya hatta belirtilmez.

Daha sonra using, çağrıyı sağlama şansını elde etmek için ifadeyi kullanmanız gerekir Dispose. Bu esasen yığınla bir sürüşe benzer (sonlandırıcı GC'ye usingolduğu gibi yığına olur).

Eksik kısım, el ile Dispose yazmanız ve onu alanlarınıza ve temel sınıfınıza çağırmanız gerektiğidir. C ++ / CLI programcıları bunu yapmak zorunda değildir. Derleyici çoğu durumda onlar için yazar.

Mükemmel yuva yapan ve threadsafe olmayan durumlar için tercih ettiğim bir alternatif var (başka bir şey dışında, IDisposable'dan kaçınmak, IDisposable'ı uygulayan her sınıfa bir finalizer eklemeye direnemeyen biriyle tartışmaya girme problemini ortadan kaldırıyor) .

Sınıf yazmak yerine bir işlev yazarsınız. İşlev, bir temsilciyi şu numarayı geri aramak üzere kabul eder:

public static void Indented(this Log log, Action action)
{
    log.Indent();
    try
    {
        action();
    }
    finally
    {
        log.Outdent();
    }
}

Ve sonra basit bir örnek şöyle olurdu:

Log.Write("Message at the top");
Log.Indented(() =>
{
    Log.Write("And this is indented");

    Log.Indented(() =>
    {
        Log.Write("This is even more indented");
    });
});
Log.Write("Back at the outermost level again");

Aktarılan lambda bir kod bloğu olarak işlev görür, bu yüzden usingarayanın onu kötüye kullanma tehlikesi artık dışında, kendi kontrol yapınızı aynı amaca hizmet etmek gibi yaparsınız. Kaynağı temizleyememelerinin bir yolu yoktur.

Bu teknik, kaynak örtüşen yaşam sürelerine sahip olabilecek bir türse daha az kullanışlıdır, çünkü daha sonra A kaynağı, sonra B kaynağı, sonra A kaynağını ve daha sonra B kaynağını öldürmek istersiniz. kullanıcıyı mükemmel şekilde yuva yapmaya zorladıysanız. Ama sonra kullanmanız gerekir IDisposable(ancak ücretsiz olmayan iş parçacığı güvenliği uygulamadıysanız, yine de bir sonlandırıcı olmadan).


re: "Dispose çağrıldıktan sonra bir nesnenin yöntemlerine başka çağrı yapılmamalıdır". Operasyon sözcüğü "Olmalıdır". Beklemede olan eşzamansız eylemleriniz varsa, nesneniz atandıktan sonra bunlar gerçekleşebilir. ObjectDisposedException özelliğine neden oluyor.
Jesse Chisholm

Sizinki, yönetilmeyen kaynakların GC'nin anlamadığı durumu kapsadığı fikrine değinen benimkinden başka tek cevap gibi görünüyor. Bununla birlikte, yönetilmeyen bir kaynağın önemli bir yönü, durumu "sahip" olan nesne olmasa bile, durumunu temizlemek isteyen bir veya daha fazla varlığın varlığını sürdürmesidir. Tanımımı nasıl buldunuz? Oldukça benzer, ama ben "kaynak" biraz daha isim-ish yapar düşünüyorum (bu hizmet için artık gerek duyulmadığı bildirimi karşılığında, dış nesne tarafından davranışını değiştirmek için "anlaşma")
supercat

@supercat - eğer ilgileniyorsanız, yukarıdaki cevabı yazdıktan birkaç gün sonra aşağıdaki gönderiyi
Daniel Earwicker

1
@DanielEarwicker: İlginç bir makale, ancak gerçekten kapsamadığınız en az bir yönetilmeyen kaynak türü düşünebilirim: uzun ömürlü nesnelerden olaylara abonelikler. Etkinlik abonelikleri telafi edilebilir, ancak bellek sınırsız olsa bile imha edilememesi maliyetli olabilir. Örneğin, numaralandırma sırasında değişiklik yapılmasına izin veren bir koleksiyonun numaralandırıcısının koleksiyondan gelen bildirimleri güncellemeye abone olması gerekebilir ve bir koleksiyon, kullanım ömrü boyunca birçok kez güncelleştirilebilir.
Sayıcılar

1
İşlem çifti enterve exitbir kaynağı nasıl düşündüğümün özüdür. Etkinliklere abone olma / abonelikten çıkma zorluğa uymamalıdır. Ortogonal / mantarlanabilir özellikler açısından bir bellek sızıntısından neredeyse ayırt edilemez. (Abonelik sadece bir listeye nesne eklediğinden bu şaşırtıcı değildir.)
Daniel Earwicker

17

IDisposable'ı kullandığım senaryolar: yönetilmeyen kaynakları temizle, etkinlikler için aboneliği iptal et, bağlantıları kapat

IDisposable ( threadsafe değil ) uygulamak için kullandığım deyim :

class MyClass : IDisposable {
    // ...

    #region IDisposable Members and Helpers
    private bool disposed = false;

    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing) {
        if (!this.disposed) {
            if (disposing) {
                // cleanup code goes here
            }
            disposed = true;
        }
    }

    ~MyClass() {
        Dispose(false);
    }
    #endregion
}


3
yönetilmeyen kaynaklarınız olmadığı sürece bir sonlandırıcı dahil edilmemelidir. O zaman bile, tercih edilen uygulama yönetilmeyen kaynağı SafeHandle içine sarmaktır.
Dave Black

11

Evet, bu kod tamamen gereksiz ve gereksizdir ve çöp toplayıcısının başka türlü yapamayacağı bir şey yapmasını sağlamaz (MyCollection'ın bir örneği kapsam dışına çıktığında, özellikle) .Clear().

Düzenlemenize cevap verin: Sırala. Bunu yaparsam:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has no Dispose() method
    instance.FillItWithAMillionStrings();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Bellek yönetimi amaçları için işlevsel olarak aynıdır:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has your Dispose()
    instance.FillItWithAMillionStrings();
    instance.Dispose();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Eğer gerçekten bu anı gerçekten boşaltmanız gerekiyorsa, arayın GC.Collect(). Yine de burada bunu yapmak için bir neden yok. Bellek gerektiğinde boşaltılır.


2
re: "Bellek gerektiğinde serbest bırakılacak." Aksine, "GC buna karar verdiğinde" deyin. GC, belleğin gerçekten gerekli olduğuna karar vermeden önce sistem performansı sorunlarını görebilirsiniz . Şimdi serbest bırakmak gerekli olmayabilir, ancak yararlı olabilir.
Jesse Chisholm

1
Bir koleksiyon içindeki referansların geçersiz kılınmasının, söz konusu öğelerin çöp toplanmasını hızlandırabileceği bazı köşe durumlar vardır. Örneğin, büyük bir dizi oluşturulur ve yeni oluşturulan daha küçük öğelere referanslarla doldurulursa, ancak bundan sonra çok uzun süre gerekli değilse, diziyi terk etmek, bu öğelerin bir sonraki Düzey 2 GC'ye kadar saklanmasına neden olabilir, ilk olarak sıfırlamak, öğeleri bir sonraki seviye 0 veya seviye 1 GC için uygun hale getirebilir. Emin olmak için, Büyük Nesne Yığını üzerinde büyük kısa ömürlü nesneler olması zaten (tasarımdan hoşlanmıyorum) icky ama ...
supercat

1
... terk etmeden önce bu tür dizileri sıfırlamak bazen GC etkisini azaltır.
supercat

11

Eğer MyCollectionzaten çöp olarak toplanan gidiyor, o zaman bunu elden gerekmez. Bunu yapmak CPU'yu gereğinden fazla çalkalayacak ve hatta çöp toplayıcının zaten gerçekleştirmiş olduğu önceden hesaplanmış bazı analizleri geçersiz kılabilir.

Kullandığım IDisposableyönetilmeyen kaynaklarla birlikte, doğru tanzim sağlamak parçacığı gibi şeyler yapmak.

EDIT Scott'ın yorumuna yanıt olarak:

GC performans metriklerinin etkilendiği tek zaman [sic] GC.Collect () çağrısı yapılması "

Kavramsal olarak GC, nesne referans grafiğinin ve ona yapılan tüm referansların iplik yığın çerçevelerinden bir görünümünü korur. Bu yığın oldukça büyük olabilir ve birçok sayfa belleği kaplayabilir. Bir optimizasyon olarak, GC, sayfayı gereksiz yere yeniden taramaktan kaçınmak için çok sık değişmesi muhtemel olmayan sayfa analizini önbelleğe alır. GC, bir sayfadaki veriler değiştiğinde çekirdekten bildirim alır, bu nedenle sayfanın kirli olduğunu ve yeniden tarama gerektirdiğini bilir. Koleksiyon Gen0'deyse, sayfadaki diğer şeylerin de değişmesi muhtemeldir, ancak bu Gen1 ve Gen2'de daha az olasıdır. Anekdot olarak, bu kancalar, Silverlight eklentisinin bu platformda çalışmasını sağlamak için GC'yi Mac'e taşıyan ekip için Mac OS X'te mevcut değildi.

Kaynakların gereksiz yere atılmasına karşı bir başka nokta: bir sürecin boşaldığı bir durumu hayal edin. Sürecin bir süredir devam ettiğini de düşünün. Muhtemelen bu işlemin bellek sayfalarının birçoğu diske değiştirilmiş olabilir. En azından artık L1 veya L2 önbelleğinde değiller. Böyle bir durumda, işlem sona erdiğinde işletim sistemi tarafından serbest bırakılacak kaynakları 'serbest bırakmak' için tüm bu veri ve kod sayfalarını tekrar belleğe değiştirmek üzere boşaltılan bir uygulamanın anlamı yoktur. Bu, yönetilen ve hatta yönetilmeyen bazı kaynaklar için geçerlidir. Yalnızca arka plan dışındaki konuları canlı tutan kaynaklar atılmalıdır, aksi takdirde işlem canlı kalacaktır.

Artık normal yürütme sırasında , yönetilmeyen bellek sızıntılarını önlemek için doğru şekilde temizlenmesi gereken geçici kaynaklar var (@fezmonkey veritabanı bağlantılarını, soketleri, pencere tutamaçlarını gösteriyor ). Bunlar bertaraf edilmesi gereken şeyler. Bir iş parçacığının sahibi olan bir sınıf oluşturursanız (ve ben de onu oluşturduğumu ve bu nedenle en azından kodlama stilimle durmasını sağlamaktan sorumlu olduğum) varsa, bu sınıf büyük olasılıkla IDisposableiş parçacığı sırasında uygulamak ve yıkmak zorundadır Dispose.

.NET çerçevesi, IDisposablearabirimi, bu sınıfın atılması gereken geliştiriciler için bir uyarı, hatta uyarı olarak kullanır . IDisposableBertarafın isteğe bağlı olduğu durumlarda (açık arabirim uygulamaları hariç) uygulanan çerçevede herhangi bir türü düşünemiyorum .


Dispose çağrısı son derece geçerli, yasal ve teşvik edilmektedir. IDisposable uygulayan nesneler genellikle bunu bir nedenden dolayı yaparlar. GC performans metriklerinin etkilendiği tek zaman GC.Collect () çağrısı yapılmasıdır.
Scott Dorman

Birçok .net sınıfı için, imha "biraz" isteğe bağlıdır, yani "genellikle" terketme durumları, yeni örnekler oluşturma ve bunları terk etme gibi deliye gitmediği sürece herhangi bir soruna neden olmaz. Örneğin, denetimler için derleyici tarafından oluşturulan kod, denetimler başlatıldığında yazı tipleri oluşturuyor ve formlar atandığında bunları terk ediyor gibi görünüyor; biri binlerce denetim yaratır ve atarsa, bu binlerce GDI tanıtıcısını bağlayabilir, ancak çoğu durumda denetimler bu kadar yaratılmaz ve yok edilmez. Yine de, böyle bir terk edilmeden kaçınmaya çalışılmalıdır.
supercat

1
Yazı tipleri söz konusu olduğunda, sorunun Microsoft'un bir kontrole atanan "font" nesnesini atmaktan hangi varlığın sorumlu olduğunu asla tanımlamamasından şüpheleniyorum; bazı durumlarda, denetimler bir fontu daha uzun ömürlü bir nesneyle paylaşabilir, bu nedenle kontrolu Fontu imha et seçeneği kötü olur. Diğer durumlarda, bir kontrole bir yazı tipi atanır ve başka bir yere atanmaz, bu nedenle kontrol onu atmazsa kimse yapmaz. Bu arada, ayrı bir tek kullanımlık FontTemplate sınıfı olsaydı, fontlarla ilgili bu zorluktan kaçınılabilirdi, çünkü kontroller Fontlarının GDI tutamacını kullanmıyor gibi görünüyor.
supercat

İsteğe bağlı Dispose()aramalar konusunda bkz. Stackoverflow.com/questions/913228/…
RJ Cuthbertson

7

Gönderdiğiniz örnekte hala "belleği şimdi boşaltmıyor". Tüm bellek çöp toplanır, ancak belleğin daha önceki bir nesilde toplanmasına izin verebilir . Emin olmak için bazı testler yapmanız gerekir.


Çerçeve Tasarımı Yönergeleri kurallar değil, yönergelerdir. Arayüzün öncelikle ne için olduğunu, ne zaman kullanılacağını, nasıl kullanılacağını ve ne zaman kullanılmayacağını söylerler.

Bir keresinde IDisposable kullanarak hata basit bir RollBack () kodu okudum. Aşağıdaki MiniTx sınıfı Dispose () işaretini kontrol eder ve Commitçağrı hiç gerçekleşmezse Rollbackkendi kendine çağırır . Arama kodunun anlaşılmasını ve bakımını daha kolay hale getiren bir dolaylama katmanı ekledi. Sonuç şuna benziyordu:

using( MiniTx tx = new MiniTx() )
{
    // code that might not work.

    tx.Commit();
} 

Ben de zamanlama / günlük kodu aynı şeyi yaptığını gördüm. Bu durumda Dispose () yöntemi zamanlayıcıyı durdurdu ve bloğun çıktığını günlüğe kaydetti.

using( LogTimer log = new LogTimer("MyCategory", "Some message") )
{
    // code to time...
}

İşte burada, yönetilmeyen kaynak temizliği yapmayan, ancak daha temiz kod oluşturmak için IDisposable'ı başarıyla kullanan birkaç somut örnek.


Daha yüksek dereceli fonksiyonları kullanarak @Daniel Earwicker örneğine bir göz atın. Kıyaslama, zamanlama, günlük tutma vb. İçin. Çok daha basit görünüyor.
Aluan Haddad


6

Yönetilmeyen kaynakları kullanma veya serbest bırakma ile ilgili olağan şeyleri tekrarlamayacağım. Fakat ortak bir yanlış anlama gibi görünen şeyleri belirtmek isterim.
Aşağıdaki kod verildiğinde

Genel Sınıf LargeStuff
  IDisposable uygular
  Özel _Large as string ()

  `` _Large '' anlamına gelen bazı garip kodlar artık birkaç milyon uzunluğunda dizgi içeriyor.

  Genel Alt Dispose () uygular IDisposable.Dispose
    _Large = Hiçbir şey
  End Sub

Tek Kullanımlık uygulamanın mevcut yönergelere uymadığını anlıyorum, ancak umarım hepiniz fikir alırsınız.
Şimdi Dispose çağrıldığında, ne kadar bellek boşaltılır?

Cevap: Yok.
Dispose çağrısı yönetilmeyen kaynakları serbest bırakabilir, yönetilen belleği geri alamaz, yalnızca GC bunu yapabilir. Yukarıdakilerin iyi bir fikir olmadığını söylemek değil, yukarıdaki kalıbı takip etmek aslında iyi bir fikir. Dispose çalıştırıldıktan sonra, LargeStuff örneği hala kapsamda olsa bile GC'nin _Large tarafından kullanılan belleği yeniden talep etmesini durduran hiçbir şey yoktur. _Large'daki dizeler de gen 0'da olabilir, ancak LargeStuff örneği gen 2 olabilir, bu nedenle bellek daha erken yeniden talep edilebilir.
Yine de yukarıda gösterilen Dispose yöntemini çağırmak için bir sonlandırıcı eklemenin bir anlamı yoktur. Bu sadece sonlandırıcının çalışmasına izin vermek için hafızanın yeniden talep edilmesini GECİKİR.


1
Bir örneği ise LargeStuffetrafında uzun Üretimi 2 bunu yapmak için yeterli ve eğer olmuştur _LargeÜretimi 0 olan bir yeni oluşturulan dize bir başvuru tutar, ardından örneği ise LargeStuffdışarı battal terk edilmesi _Large, ardından dize ile anılır _Largebir sonraki Gen2 koleksiyonuna kadar saklanacak. Sıfırlama _Largeişlemi, dizenin bir sonraki Gen0 koleksiyonunda elenmesine izin verebilir. Çoğu durumda, referansları iptal etmek yardımcı olmaz, ancak bazı faydalar sağlayabileceği durumlar vardır.
supercat

5

Apart kontrol etmek için bir yol olarak birincil kullanımı ömrünü ait sistem kaynaklarının (tamamen müthiş cevap kapsadığı Ian , şeref!), Idisposable / kullanarak combo da kullanılabilir kapsam (kritik) küresel kaynakların durum değişikliği : konsol , ipler , işlem , herhangi bir genel amacı, bir gibi uygulama örneği .

Bu kalıp hakkında bir makale yazdım: http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

Sık kullanılan bazı küresel durumları yeniden kullanılabilir ve okunabilir bir şekilde nasıl koruyabileceğinizi gösterir : konsol renkleri , geçerli iş parçacığı kültürü , Excel uygulama nesnesi özellikleri ...


4

Bir şey varsa, kodun dışarıda bırakıldığında olduğundan daha az verimli olmasını beklerdim .

Clear () yöntemlerini çağırmak gereksizdir ve Dispose bunu yapmazsa GC muhtemelen bunu yapmaz ...


2

Dispose()İşlemin örnek kodda , nesnenin normal GC'si nedeniyle oluşmayacak bir etkisi olabilecek şeyler vardır MyCollection.

Başvurulan _theListveya _theDictbaşka nesneler tarafından atıfta bulunulan nesneler, bu nesne List<>veya Dictionary<>nesnenin koleksiyonuna tabi olmayacak, ancak aniden içeriğe sahip olmayacaktır. Örnekteki gibi Dispose () işlemi olmasaydı, bu koleksiyonlar hala içeriklerini içerecektir.

Bu durum olsaydı Tabii ki, bunu bir kırık tasarım çağırır - Sadece işaret ediyorum (ukalalıkla herhalde) o Dispose()operasyon tamamen gereksiz olmayabilir, diğer kullanımları olup olmamasına bağlı olarak List<>ya Dictionary<>o değil parçasında gösterilmiştir.


Onlar özel alanlar, bu yüzden OP'nin onlara referans vermediğini varsaymanın adil olduğunu düşünüyorum.
mqp

1) kod parçası sadece örnek kod, bu yüzden sadece göz ardı kolay bir yan etkisi olabilir işaret; 2) özel alanlar genellikle bir alıcı mülkünün / yönteminin hedefidir - belki çok fazladır (alıcı / ayarlayıcılar bazı insanlar tarafından bir anti-desen olarak kabul edilir).
Michael Burr

2

"Yönetilmeyen kaynaklar" hakkındaki çoğu tartışmayla ilgili bir sorun, terimi gerçekten tanımlamamaları, ancak yönetilmeyen kodla ilgili bir şeyleri olduğu anlamına geliyor gibi görünmektedir. Birçok yönetilmeyen kaynak türünün yönetilmeyen kodla arabirim oluşturduğu doğru olsa da, yönetilmeyen kaynakların bu terimlerle düşünülmesi yararlı değildir.

Bunun yerine, tüm yönetilen kaynakların ortak noktası ne olmalıdır: hepsi kendi adına bir şey yapmasını, diğer bazı şeylerin aleyhine bir şey yapmasını isteyen diğer bir nesneyi ve bunu yapmayı kabul eden diğer varlık ek uyarı. Nesne iz bırakmadan terk edilecek ve yok olacak olsaydı, hiçbir şey 'şey'in dışında artık var olmayan nesne adına davranışını değiştirmeye gerek olmadığını söyleyemezdi; sonuç olarak, 'şeyin faydası kalıcı olarak azalacaktır.

Yönetilmeyen bir kaynak, o zaman, bir nesne adına davranışını değiştirmek için dışarıdaki bazı 'şeylerin' yaptığı bir anlaşmayı temsil eder; bu, nesne terkedilip varolmadığı takdirde, o 'şey'in dışındaki faydasını işe yaramaz. Yönetilen kaynak, böyle bir anlaşmanın faydalanıcısı olan, ancak terkedilirse bildirim almak için kaydolan ve bu bildirimi, yok edilmeden önce işlerini düzene koymak için kullanacak bir nesnedir.


IMO, yönetilmeyen nesnenin tanımı açık; GC olmayan herhangi bir nesne .
Eonil

1
@Eonil: Manmaned Object! = Yönetilmeyen Kaynak. Olaylar gibi şeyler tamamen yönetilen nesneler kullanılarak uygulanabilir, ancak yine de yönetilmeyen kaynaklar oluşturur, çünkü - en azından uzun ömürlü nesnelerin olaylarına abone olan kısa ömürlü nesneler durumunda - GC bunları nasıl temizleyeceği hakkında hiçbir şey bilmez .
supercat


2

İlk tanım. Benim için yönetilmeyen kaynak IDisposable arayüzü veya dll çağrıları kullanımı ile oluşturulan bir şey uygulayan bazı sınıf anlamına gelir. GC bu tür nesnelerle nasıl başa çıkılacağını bilmiyor. Sınıf örneğin yalnızca değer türleri varsa, bu sınıf yönetilmeyen kaynaklara sahip sınıf olarak düşünmüyorum. Kodum için sonraki uygulamaları takip ediyorum:

  1. Eğer benim tarafımdan oluşturulan sınıf yönetilmeyen bazı kaynaklar kullanıyorsa, o zaman ben de bellek temizlemek için IDisposable arayüzü uygulamak gerekir anlamına gelir.
  2. Kullanmayı bitirir bitirmez nesneleri temizleyin.
  3. Dispose yöntemimde, sınıfın tüm IDisposable üyeleri üzerinde yineleme yapıyorum ve Dispose çağrısı yapıyorum.
  4. Dispose yöntemimde, çöp toplayıcıya nesnemin zaten temizlendiğini bildirmek için GC.SuppressFinalize (this) öğesini çağırın. Bunu yapıyorum çünkü GC'yi çağırmak pahalı bir işlemdir.
  5. Ek önlem olarak Dispose () işlevini birden çok kez çağırmayı deniyorum.
  6. Bazen özel üye _disposed ekleyin ve yöntem çağrıları check-in nesne temizlendi mi. Ve temizlendiyse daha sonra ObjectDisposedException oluşturun
    Aşağıdaki şablon, kod örneği olarak kelimelerde açıkladığımı gösterir:

public class SomeClass : IDisposable
    {
        /// <summary>
        /// As usually I don't care was object disposed or not
        /// </summary>
        public void SomeMethod()
        {
            if (_disposed)
                throw new ObjectDisposedException("SomeClass instance been disposed");
        }

        public void Dispose()
        {
            Dispose(true);
        }

        private bool _disposed;

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;
            if (disposing)//we are in the first call
            {
            }
            _disposed = true;
        }
    }

1
"Benim için yönetilmeyen kaynak IDisposable arayüzü veya dll çağrıları kullanımı ile oluşturulan bir şey uygulayan bir sınıf anlamına gelir." Yani kendiniz is IDisposableyönetilmeyen bir kaynak olarak düşünülmesi gereken herhangi bir tür mü diyorsunuz ? Bu doğru görünmüyor. Ayrıca implmenting türü saf bir değer türü ise, atılmasına gerek olmadığını düşündürür. Bu da yanlış görünüyor.
Aluan Haddad

Herkes kendi başına yargılar. Sadece ekleme uğruna benim kodumu bir şey eklemek istemiyorum. IDisposable'ı eklersem, GC'nin yönetemediği bir tür işlevsellik oluşturduğum anlamına gelir veya sanırım ömrünü düzgün bir şekilde yönetemeyecektir.
Yuriy Zaletskyy

2

Verilen kod örneğiniz IDisposablekullanım için iyi bir örnek değil . Sözlük temizliği normaldeDispose yönteme gitmemelidir . Sözlük öğeleri kapsam dışı olduğunda silinecek ve bertaraf edilecektir. IDisposableuygulama, kapsam dışı olduktan sonra bile serbest bırakılmayacak / serbest bırakılmayacak bazı bellek / işleyicileri serbest bırakmak için gereklidir.

Aşağıdaki örnek, bazı kod ve açıklamalara sahip IDisposable desen için iyi bir örnek göstermektedir.

public class DisposeExample
{
    // A base class that implements IDisposable. 
    // By implementing IDisposable, you are announcing that 
    // instances of this type allocate scarce resources. 
    public class MyResource: IDisposable
    {
        // Pointer to an external unmanaged resource. 
        private IntPtr handle;
        // Other managed resource this class uses. 
        private Component component = new Component();
        // Track whether Dispose has been called. 
        private bool disposed = false;

        // The class constructor. 
        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }

        // Implement IDisposable. 
        // Do not make this method virtual. 
        // A derived class should not be able to override this method. 
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method. 
            // Therefore, you should call GC.SupressFinalize to 
            // take this object off the finalization queue 
            // and prevent finalization code for this object 
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios. 
        // If disposing equals true, the method has been called directly 
        // or indirectly by a user's code. Managed and unmanaged resources 
        // can be disposed. 
        // If disposing equals false, the method has been called by the 
        // runtime from inside the finalizer and you should not reference 
        // other objects. Only unmanaged resources can be disposed. 
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called. 
            if(!this.disposed)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources. 
                if(disposing)
                {
                    // Dispose managed resources.
                    component.Dispose();
                }

                // Call the appropriate methods to clean up 
                // unmanaged resources here. 
                // If disposing is false, 
                // only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;

                // Note disposing has been done.
                disposed = true;

            }
        }

        // Use interop to call the method necessary 
        // to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

        // Use C# destructor syntax for finalization code. 
        // This destructor will run only if the Dispose method 
        // does not get called. 
        // It gives your base class the opportunity to finalize. 
        // Do not provide destructors in types derived from this class.
        ~MyResource()
        {
            // Do not re-create Dispose clean-up code here. 
            // Calling Dispose(false) is optimal in terms of 
            // readability and maintainability.
            Dispose(false);
        }
    }
    public static void Main()
    {
        // Insert code here to create 
        // and use the MyResource object.
    }
}

1

Yönetilen kaynakların elden çıkarılması için en haklı kullanım örneği, GC'nin aksi takdirde asla toplanmayacak kaynakları geri almaya hazırlığıdır.

Buna en iyi örnek dairesel referanslardır.

Dairesel referanslardan kaçınan kalıpları kullanmak en iyi uygulama olsa da, (örneğin) 'ebeveyni' için bir referansı olan bir 'alt' nesneyle sonuçlanırsanız, bu sadece terk ederseniz ebeveynin GC koleksiyonunu durdurabilir referans ve GC'ye güvenin - artı bir sonlandırıcı uyguladıysanız, asla çağrılmaz.

Bunun tek yolu, çocuklarda Ebeveyn referanslarını null değerine ayarlayarak dairesel referansları manuel olarak kırmaktır.

Ebeveynler ve çocuklar için tek kullanımlık ID'yi uygulamak bunu yapmanın en iyi yoludur. Üst öğede Atma çağrıldığında, Tüm Çocuklarda Atma öğesini çağırın ve alt öğeyi Atma yönteminde, Üst referansları null olarak ayarlayın.


4
Çoğunlukla, GC ölü nesneleri tanımlayarak değil, canlı nesneleri tanımlayarak çalışır. Her gc döngüsünden sonra, finallizasyon için kaydedilen her nesne için büyük nesne yığınında depolanır veya canlı bir hedeftir WeakReference, sistem son GC döngüsünde canlı köklü bir referansın bulunduğunu gösteren bir bayrağı kontrol eder. ve nesneyi hemen sonlandırılması gereken bir nesne kuyruğuna ekler, nesneyi büyük nesne yığınından serbest bırakır veya zayıf başvuruyu geçersiz kılar. Dairesel referanslar, başka bir referans yoksa nesneleri canlı tutmaz.
supercat

1

Hem yönetilen hem de yönetilmeyen kaynaklar için IDisposable kullanımı hakkında konuşmak için birçok yanıtın değiştiğini görüyorum. Bu makaleyi, IDisposable'ın gerçekten nasıl kullanılması gerektiğine ilişkin bulduğum en iyi açıklamalardan biri olarak öneriyorum.

https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About

Asıl soru için; çok fazla bellek alan yönetilen nesneleri temizlemek için IDisposable'ı kullanırsanız kısa yanıt hayır olacaktır . Bunun nedeni, bir kez kullanılıp atılamaz duruma getirdiğinizde kapsamının dışına çıkmasına izin vermenizdir. Bu noktada, başvurulan alt nesneler de kapsam dışıdır ve toplanır.

Bunun tek istisnası, yönetilen nesnelerde çok fazla belleğiniz varsa ve bu işlemin tamamlanmasını bekleyen bir iş parçacığını bloke etmeniz olabilir. Bu çağrı tamamlandıktan sonra gerekmeyecek nesneler, bu referansları null olarak ayarlamak, çöp toplayıcının onları daha erken toplamasına izin verebilir. Ancak bu senaryo, yeniden düzenlenmesi gereken hatalı kodu temsil eder - IDisposable'ın bir kullanım durumu değil.


1
Neden bazılarının cevabına -1 koyduğunu anlamadım
Sebastian Oscar Lopez
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.