C # 'da Sonlandırma / Atma yönteminin kullanılması


381

C # 2008

Bir süredir bunun üzerinde çalışıyorum ve hala kodda sonlandırma ve atma yöntemlerinin kullanımı hakkında kafam karıştı. Sorularım aşağıda:

  1. Yönetilmeyen kaynakları elden çıkarırken yalnızca bir sonlandırıcıya ihtiyacımız olduğunu biliyorum. Ancak, yönetilmeyen kaynaklara çağrı yapan yönetilen kaynaklar varsa, yine de bir sonlandırıcı uygulaması gerekir mi?

  2. Ancak, herhangi bir yönetilmeyen kaynak kullanmayan bir sınıf geliştirirsem - doğrudan veya dolaylı olarak, IDisposablebu sınıftaki istemcilerin 'using deyimini' kullanmasına izin vermek için şunu uygulamalıyım ?

    Yalnızca sınıfınızın istemcilerinin using deyimini kullanmasını sağlamak için IDisposable'ı uygulamak mümkün müdür?

    using(myClass objClass = new myClass())
    {
        // Do stuff here
    }
  3. Sonlandırma / atma kullanımını göstermek için aşağıdaki basit kodu geliştirdim:

    public class NoGateway : IDisposable
    {
        private WebClient wc = null;
    
        public NoGateway()
        {
            wc = new WebClient();
            wc.DownloadStringCompleted += wc_DownloadStringCompleted;
        }
    
    
        // Start the Async call to find if NoGateway is true or false
        public void NoGatewayStatus()
        {
            // Start the Async's download
                // Do other work here
            wc.DownloadStringAsync(new Uri(www.xxxx.xxx));
        }
    
        private void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            // Do work here
        }
    
        // Dispose of the NoGateway object
        public void Dispose()
        {
            wc.DownloadStringCompleted -= wc_DownloadStringCompleted;
            wc.Dispose();
            GC.SuppressFinalize(this);
        }
    }

Kaynak kodu hakkında soru:

  1. Burada sonlandırıcıyı eklemedim ve normalde sonlandırıcı GC tarafından çağrılacak ve sonlandırıcı Dispose olarak adlandırılacaktır. Sonlandırıcıya sahip olmadığım için ne zaman Dispose yöntemini çağırırım? Sınıfın istemesi gereken istemci mi?

    Yani örnekteki sınıfım NoGateway olarak adlandırılır ve istemci sınıfı aşağıdaki gibi kullanabilir ve atabilir:

    using(NoGateway objNoGateway = new NoGateway())
    {
        // Do stuff here   
    }

    Yürütme, kullanım bloğunun sonuna geldiğinde Dispose yöntemi otomatik olarak çağrılır mı veya istemcinin dispose yöntemini el ile çağırması mı gerekir? yani

    NoGateway objNoGateway = new NoGateway();
    // Do stuff with object
    objNoGateway.Dispose(); // finished with it
  2. WebClientSınıfı sınıfımda kullanıyorum NoGateway. Çünkü WebClientuygular IDisposablearayüzünde bu demek WebClientdolaylı yönetilmeyen kaynakları kullanır? Bunu takip etmek zor ve hızlı bir kural var mı? Bir sınıfın yönetilmeyen kaynaklar kullandığını nasıl bilebilirim?


1
Bu kaynak tasarım sorununu çözmek için bu karmaşık tasarım deseni gerçekten gerekli mi?
zinking

Yanıtlar:


422

Önerilen IDisposable desen burada . IDisposable kullanan bir sınıfı programlarken, genellikle iki desen kullanmalısınız:

Yönetilmeyen kaynakları kullanmayan kapalı bir sınıf uygularken, normal arabirim uygulamalarında olduğu gibi bir Dispose yöntemi uygulamanız yeterlidir:

public sealed class A : IDisposable
{
    public void Dispose()
    {
        // get rid of managed resources, call Dispose on member variables...
    }
}

Mühürsüz bir sınıfı uygularken şöyle yapın:

public class B : IDisposable
{    
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // get rid of managed resources
        }   
        // get rid of unmanaged resources
    }

    // only if you use unmanaged resources directly in B
    //~B()
    //{
    //    Dispose(false);
    //}
}

Bir finalizer ilan etmediğime dikkat edin B; bir sonlandırıcıyı yalnızca imha edilecek gerçek yönetilmeyen kaynaklarınız varsa uygulamalısınız. CLR, sonlandırılabilir nesnelerle, çağrılsa bile, sonlandırılamayan nesnelerle farklı şekilde ilgilenir SuppressFinalize.

Bu nedenle, zorunlu olmadıkça bir finalizer ilan etmemelisiniz, ancak sınıfınızın mirasçılarına, Disposeyönetilmeyen kaynakları doğrudan kullanıyorlarsa , sizi aramak ve bir finalizer uygulamak için bir kanca verirsiniz :

public class C : B
{
    private IntPtr m_Handle;

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            // get rid of managed resources
        }
        ReleaseHandle(m_Handle);

        base.Dispose(disposing);
    }

    ~C() {
        Dispose(false);
    }
}

Yönetilmeyen kaynakları doğrudan kullanmıyorsanız ( SafeHandleve arkadaşlar kendi sonlandırıcılarını beyan ettikleri için saymazlar), daha sonra sonlandırıcıyı bastırsanız bile GC sonlandırılabilir sınıflarla farklı bir şekilde uğraştığından bir sonlandırıcı uygulamayın. Ayrıca B, bir sonlandırıcı içermemesine rağmen , yine de bir sonlandırıcı SuppressFinalizeuygulayan alt sınıflarla doğru bir şekilde ilgilenmeye çağırdığını unutmayın .

Bir sınıf IDisposable arabirimini uyguladığında, bir yerde sınıfı kullanmayı bitirdiğinizde kurtulmanız gereken bazı yönetilmeyen kaynaklar olduğu anlamına gelir. Gerçek kaynaklar sınıflar içinde kapsüllenir; bunları açıkça silmenize gerek yoktur. Dispose()Sınıfı çağırmak veya bir sınıfa sarmak, using(...) {}yönetilmeyen kaynakların gerektiği gibi ortadan kaldırılmasını sağlayacaktır.


26
Katılıma katılıyorum. Yalnızca yönetilen kaynaklarla uğraşıyorsanız bir sonlandırıcıya ihtiyacınız olmadığını unutmayın (aslında, sonlandırıcınızdan ("bu" dışında) yönetilen nesnelere erişmeye çalışmamalısınız, çünkü GC, nesneleri temizler.Ayrıca, .Net 2.0 veya daha iyisini kullanıyorsanız, yönetilmeyen tutamaçları sarmak için SafeHandles'i kullanabilirsiniz (ve kullanmalısınız) Safehandles, yönetilen sınıflarınız için sonlandırıcılar yazma gereksiniminizi büyük ölçüde azaltır . com.tr / bclteam / arşiv / 2005/03/16 / 396900.aspx
JMarsch

5
Sonlandırıcıda MessageBox.Show ("Hata," + GetType (). Name + "atılmamış") çağrısı koymak daha iyidir, çünkü tek kullanımlık nesne her zaman bertaraf edilmelidir ve bunu yapamazsanız en iyi şekilde gerçeğe uyarılması en iyisidir.
erikkallen

95
@erikkallen bu bir şaka mı? :)
Alex Norcliffe

2
çünkü aktif finalizörlere sahip sınıfları takip etmek için CLR'de ekstra hesaplama çabalarına ihtiyaç vardır. - Sonlandırıcı uygulamak bunun olmasına neden olur. GC.SuppressFinalize öğesinin çağrılması, Sonlandırıcı'nın çalışma zamanı tarafından çağrılmaması gerektiği anlamına gelir. Ne olursa olsun hala Gen2 gidiyor. Yönetilen kaynaklarla ilgilenmiyorsanız bir sonlandırıcı eklemeyin. Mühürlü veya mühürsüz sınıf değiştiricileri bu nokta ile ilgisizdir.
Mart'ta Melton'a Ritch

3
@Ritch: alıntı? Bu mutlaka kötü bir şey değildir; Eğer uyguluyorsanız IDisposable, zaten bir süre takılma ihtimali vardır. CLR'yi Gen0 -> Gen1 -> Gen2
thecoop

123

Uygulanacak resmi kalıbı IDisposableanlamak zor. Bunun daha iyi olduğuna inanıyorum :

public class BetterDisposableClass : IDisposable {

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

  protected virtual void CleanUpManagedResources() { 
    // ...
  }
  protected virtual void CleanUpNativeResources() {
    // ...
  }

  ~BetterDisposableClass() {
    CleanUpNativeResources();
  }

}

Daha da iyi bir çözüm, yönetmeniz gereken herhangi bir yönetilmeyen kaynak için her zaman bir sarmalayıcı sınıfı oluşturmanız gereken bir kurala sahip olmaktır:

public class NativeDisposable : IDisposable {

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

  protected virtual void CleanUpNativeResource() {
    // ...
  }

  ~NativeDisposable() {
    CleanUpNativeResource();
  }

}

İle SafeHandleve türevleri, bu sınıflar olmalı çok nadir .

Yönetilmeyen kaynaklarla doğrudan miras kalmayan tek kullanımlık sınıfların sonucu, miras varlığında bile güçlüdür: artık yönetilmeyen kaynaklarla ilgilenmeleri gerekmiyor . Onlar olacağım basit uygulamak ve anlamak:

public class ManagedDisposable : IDisposable {

  public virtual void Dispose() {
    // dispose of managed resources
  }

}

@Kyle: Teşekkürler! Ben de beğendim :-) Burada bir takip var .
Jordão

4
Not etmek istediğim bir şey olsa da, ikinci kez çağrılmasını engellemiyor.
HüseyinUslu

5
@ HüseyinÜslu: Bu sadece desenin özü . Kesinlikle bir disposedbayrak ekleyebilir ve buna göre kontrol edebilirsiniz.
Jordão

2
@didibus: disposedBayrak eklemek basit bir meseledir, atmadan önce kontrol edin ve attıktan sonra ayarlayın. Fikir için buraya bakın . Sınıfın herhangi bir yönteminden önce bayrağı da kontrol etmelisiniz. Mantıklı? Karmaşık mı?
Jordão

1
+1 "Daha da iyi bir çözüm, yönetmeniz gereken herhangi bir yönetilmeyen kaynak için her zaman bir sarıcı sınıf oluşturmanız gereken bir kurala sahip olmaktır" . VLC için bir eklenti içinde tökezledi ve o zamandan beri kullanıyorum. Çok fazla baş ağrısından
Franz B.

37

Tek kullanımlık herhangi bir uygulamanın aşağıdaki modeli (IMHO) izlemesi gerektiğini unutmayın. Birkaç mükemmel .NET "tanrıları" .NET Framework Tasarım Yönergeleri (MSDN nedense bu nedenle takip etmiyor!) Bilgi dayalı bu desen geliştirdi . .NET Framework Tasarım Yönergeleri Krzysztof Cwalina (o sırada CLR Architect) ve Brad Abrams (o sırada CLR Program Yöneticisi'ne inanıyorum) ve Bill Wagner ([Etkili C #] ve [Daha Etkili C #] (sadece Amazon.com'da bunları arayın:

Sınıfınız doğrudan Yönetilmeyen kaynakları içermiyorsa (devralmazsa) ASLA bir Sonlandırıcı uygulamamalısınız. Bir sınıfta bir Finalizer uyguladığınızda, hiç çağrılmasa bile, ekstra bir koleksiyon için yaşamanın garantisi vardır. Otomatik olarak Sonlandırma Kuyruğuna (tek bir iş parçacığında çalışır) yerleştirilir. Ayrıca, çok önemli bir not ... bir Finalizer içinde yürütülen tüm kodlar (bir tane uygulamanız gerekiyorsa) iş parçacığı güvenli ve istisna güvenli olmalıdır ZORUNLU! KÖTÜ şeyler başka türlü gerçekleşecektir ... (yani belirlenemeyen davranış ve istisna durumunda, kurtarılamaz bir ölümcül uygulama çökmesi).

Bir araya getirdiğim (ve bir kod snippet'i yazdığım) desen şöyle:

#region IDisposable implementation

//TODO remember to make this class inherit from IDisposable -> $className$ : IDisposable

// Default initialization for a bool is 'false'
private bool IsDisposed { get; set; }

/// <summary>
/// Implementation of Dispose according to .NET Framework Design Guidelines.
/// </summary>
/// <remarks>Do not make this method virtual.
/// A derived class should not be able to override this method.
/// </remarks>
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.

    // Always use SuppressFinalize() in case a subclass
    // of this type implements a finalizer.
    GC.SuppressFinalize( this );
}

/// <summary>
/// Overloaded Implementation of Dispose.
/// </summary>
/// <param name="isDisposing"></param>
/// <remarks>
/// <para><list type="bulleted">Dispose(bool isDisposing) executes in two distinct scenarios.
/// <item>If <paramref name="isDisposing"/> equals true, the method has been called directly
/// or indirectly by a user's code. Managed and unmanaged resources
/// can be disposed.</item>
/// <item>If <paramref name="isDisposing"/> 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.</item></list></para>
/// </remarks>
protected virtual void Dispose( bool isDisposing )
{
    // TODO If you need thread safety, use a lock around these 
    // operations, as well as in your methods that use the resource.
    try
    {
        if( !this.IsDisposed )
        {
            if( isDisposing )
            {
                // TODO Release all managed resources here

                $end$
            }

            // TODO Release all unmanaged resources here



            // TODO explicitly set root references to null to expressly tell the GarbageCollector
            // that the resources have been disposed of and its ok to release the memory allocated for them.


        }
    }
    finally
    {
        // explicitly call the base class Dispose implementation
        base.Dispose( isDisposing );

        this.IsDisposed = true;
    }
}

//TODO Uncomment this code if this class will contain members which are UNmanaged
// 
///// <summary>Finalizer for $className$</summary>
///// <remarks>This finalizer will run only if the Dispose method does not get called.
///// It gives your base class the opportunity to finalize.
///// DO NOT provide finalizers in types derived from this class.
///// All code executed within a Finalizer MUST be thread-safe!</remarks>
//  ~$className$()
//  {
//     Dispose( false );
//  }
#endregion IDisposable implementation

İşte türetilmiş bir sınıfta IDisposable uygulamak için kod. Türetilmiş sınıfın tanımında IDisposable'dan devralmayı açıkça listelemeniz gerekmediğini unutmayın.

public DerivedClass : BaseClass, IDisposable (remove the IDisposable because it is inherited from BaseClass)


protected override void Dispose( bool isDisposing )
{
    try
    {
        if ( !this.IsDisposed )
        {
            if ( isDisposing )
            {
                // Release all managed resources here

            }
        }
    }
    finally
    {
        // explicitly call the base class Dispose implementation
        base.Dispose( isDisposing );
    }
}

Bu uygulamayı blogumda şu adresten yayınladım: Nasıl Kullanılır Deseni Düzgün Uygulama


Birisi de türetilmiş bir sınıf için bir desen ekleyebilir miyim (bu temel sınıftan türetilmiş)
akjoshi

3
@akjoshi - Türetilmiş atılabilir sınıfın kodunu içerecek şekilde yukarıdaki deseni güncelledim. Ayrıca, türetilmiş bir sınıfta ASLA bir Finalizer uygulamayın ...
Dave Black

3
Microsoft, atılan yöntemin sonunda "imha" bayrağını ayarlamak gibi görünüyor, ama bu benim için yanlış görünüyor. "Atma" için yapılan gereksiz çağrıların hiçbir şey yapmaması gerekir; normalde Dispose'in özyinelemeli olarak çağrılmasını beklemezken, inşaat veya başka bir işlem sırasında oluşan bir istisna ile geçersiz durumda bırakılan bir nesneyi Atmaya çalışıyorsa bu tür şeyler olabilir. Sanal olmayan sarıcı işlevinde Interlocked.Exchangebir tamsayı IsDisposedbayrağı kullanarak daha güvenli olacağını düşünüyorum.
supercat

@DaveBlack: Temel sınıfınız yönetilmeyen kaynakları kullanmıyorsa, ancak türetilmiş sınıfınız kullanıyorsa ne olur? Finalizer'ı türetilmiş sınıfa uygulamak zorunda mısınız? Eğer öyleyse, kaynağa erişiminiz yoksa temel sınıfın bunu zaten uygulamadığını nasıl anlarsınız?
Didier A.

@DaveBlack "Bu kalıbı birkaç mükemmel .NET" tanrı "dan gelen bilgilere dayanarak geliştirdim.
Elisabeth

23

Ben pm100 ile katılıyorum (ve daha önceki yazımda bunu açıkça söylemeliydim).

İhtiyacınız olmadığı sürece bir sınıfta asla IDisposable kullanmamalısınız. Çok spesifik olmak gerekirse, IDisposable'ı hiç ihtiyaç duyduğunuz / uygulamanız gereken yaklaşık 5 kez vardır:

  1. Sınıfınız açıkça IDisposable uygulayan ve sınıfınız artık kullanılmadığında temizlenecek yönetilen kaynakları açıkça içerir (miras yoluyla değil). Örneğin, sınıfınız bir Stream, DbCommand, DataTable vb.'nin bir örneğini içeriyorsa.

  2. Sınıfınız açıkça bir Close () yöntemi uygulayan yönetilen kaynakları içerir - örn. IDataReader, IDbConnection, vb. Bu sınıflardan bazılarının Dispose () yönteminin yanı sıra bir Close () yöntemine sahip olarak IDisposable uyguladığına dikkat edin.

  3. Sınıfınız açıkça yönetilmeyen bir kaynak içeriyor - örn. Bir COM nesnesi, işaretçiler (evet, yönetilen C # 'da işaretçiler kullanabilirsiniz, ancak' güvenli olmayan 'bloklar, vb. Olarak bildirilmelidirler. Yönetilmeyen kaynaklar söz konusu olduğunda, RCW'de System.Runtime.InteropServices.Marshal.ReleaseComObject () öğesini çağırın RCW, teorik olarak, yönetilen bir sarıcı olmasına rağmen, kapakların altında devam eden referans sayımı vardır.

  4. Sınıfınız güçlü referanslar kullanan etkinliklere abone olursa. Kendinizi etkinliklerden silmeniz / silmeniz gerekir. Bunların kaydını silmeye / ayırmaya çalışmadan önce her zaman bunların boş olmadığından emin olmak için !.

  5. Sınıfınız yukarıdakilerin herhangi bir kombinasyonunu içerir ...

COM nesneleri ile çalışmak ve Marshal.ReleaseComObject () kullanmak zorunda kalmanız için önerilen bir alternatif System.Runtime.InteropServices.SafeHandle sınıfını kullanmaktır.

BCL'nin (Base Class Library Team) burada iyi bir blog yazısı var http://blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx

Yapılması gereken çok önemli bir nokta, WCF ile çalışıyorsanız ve kaynakları temizliyorsanız, DAİMA 'kullanma' bloğundan kaçınmalısınız. MSDN'de bunun neden kötü bir fikir olduğu konusunda çok sayıda blog yayını var. Ben de bu konuda burada yayınlanmıştır - WCF proxy ile 'using ()' kullanmayın


3
Beşinci durum olduğuna inanıyorum: Sınıfınız güçlü referanslar kullanan etkinliklere abone oluyorsa, IDisposable'ı uygulamalı ve Dispose yöntemindeki olaylardan kendinizi silmelisiniz.
Didier

Merhaba didibus. Evet haklısın. O şeyi unuttum. Cevabımı bir vaka olarak ekleyecek şekilde değiştirdim. Teşekkürler.
Dave Black

Atma kalıbı için MSDN belgeleri başka bir durum daha ekler: "Yönetilmeyen kaynaklar veya tek kullanımlık nesneler içermeyen, ancak alt türleri olması muhtemel sınıflarda Temel Atma Kalıbı'nı uygulayan CONSIDER. Bunun en iyi örneği System.IO'dur. .Stream class. Kaynak içermeyen soyut bir temel sınıf olmasına rağmen, alt sınıflarının çoğu bunu yapar ve bu nedenle bu kalıbı uygular. "
Gittim

12

IDisposable yerine lambdas kullanma.

Ben asla / IDisposable fikri kullanarak heyecan duymadım. Sorun, arayanın şunları yapmasını gerektirmesidir:

  • IDisposable kullanmaları gerektiğini biliyorum
  • 'kullanarak' kullanmayı unutmayın.

Yeni tercih ettiğim yöntem, bunun yerine fabrika yöntemi ve lambda kullanmak

SqlConnection ile bir şey yapmak istediğinizi düşünün (bir kullanımda sarılması gereken bir şey). Klasik olarak yapardın

using (Var conn = Factory.MakeConnection())
{
     conn.Query(....);
}

Yeni yol

Factory.DoWithConnection((conn)=>
{
    conn.Query(...);
}

İlk durumda arayan, sözdizimini kullanarak kullanamaz. İkinci durumda kullanıcının seçeneği yoktur. SqlConnection nesnesi oluşturan bir yöntem yoktur, çağıranın DoWithConnection öğesini çağırması gerekir.

DoWithConnection şöyle görünür

void DoWithConnection(Action<SqlConnection> action)
{
   using (var conn = MakeConnection())
   {
       action(conn);
   }
}

MakeConnection şimdi özel


2
Lambdalara bir şeyler sarmak iyi bir yaklaşım olabilir, ancak sınırları vardır. Aslında, bir sınıfın tüm tüketicilerinin bir "kullanma" bloğu kullanacağı durumlar için çok kötü değil, ancak bir yöntemin sınıf alanında (doğrudan veya yineleyici gibi bir şey) Tek Kullanımlık bir depolayacağı durumlara izin vermeyecektir ).
supercat

@supercat kaynak biriken şeylerin depolanmasına izin vermemenin İyi Bir Şey olduğunu iddia edebilirsiniz. Burada önerdiğim borçlanma modeli sizi kaynak kullanımınız konusunda yalın olmaya zorlar
pm100

İyi bir şey olabilir, ancak bazı makul işlemleri de çok zorlaştırabilir. Örneğin, bir veritabanı okuyucu türünün, IEnumerable <T> uygulamak yerine DoForAll(Action<T>) where T:IComparable<T>, her kayıtta belirtilen temsilci çağıran bir yöntem sunduğunu varsayalım . Her ikisi de verileri sıralanmış bir sırayla döndürecek olan bu tür iki nesne göz önüne alındığında, biri bir koleksiyonda var olan tüm öğeleri nasıl verir, diğeri değil? Türler uygulanırsa IEnumerable<T>, bir birleştirme işlemi gerçekleştirilebilir, ancak bu çalışmaz DoForAll.
supercat

İki DoForAllkoleksiyonun tamamını, bir bütün olarak başka bir yapıya kopyalamak zorunda kalmadan birleştirmenin tek yolu , sadece bir çift IEnumerable's kullanmaktan ve dikkatli olmaktan ziyade kaynaklardan daha fazla hoggish olacak iki iplik kullanmak olacaktır. onları serbest bırakmak için.
supercat

-1: sorulmamış bir soruya iyi cevap. Bu "Tek kullanımlık nesnelerin tüketimini nasıl kolaylaştırabilirim" için harika bir cevap olacaktır
John Saunders

10

kimse ihtiyaç duymasanız bile IDisposable'ı uygulayıp uygulayamayacağınız sorusuna cevap vermedi.

Kısa cevap: Hayır

Uzun cevap:

Bu, sınıfınızdaki bir tüketicinin 'kullanarak' kullanmasına olanak tanır. Sorduğum soru - neden yapsınlar? Çoğu geliştirici, bilmeleri gerekmediğini ve nasıl bildiklerini bilmedikçe 'kullanma'yı kullanmayacaktır. ya

  • deneyimlerinden onları alır (örneğin soket sınıfı)
  • belgelenmiş
  • temkinli ve sınıfın IDisposable'ı uyguladığını görebiliyorlar

IDisposable'ı uygulayarak devs'e (en azından bazıları) bu sınıfın serbest bırakılması gereken bir şeyi tamamladığını söylüyorsunuz. 'Using' kullanırlar - ancak kullanımın mümkün olmadığı başka durumlar da vardır (nesnenin kapsamı yerel değildir); ve diğer durumlarda nesnelerin ömrü hakkında endişelenmeye başlayacaklar - kesinlikle endişelenirim. Ama bu gerekli değil

Idisposable'ı kullanarak kullanmalarını sağlamak için uygularsınız, ancak siz söylemedikçe kullanmazlar.

Bu yüzden yapma


1
Bir geliştirici neden IDisposable uygulayan bir nesne kullanarak / imha kullanmak olmaz anlamıyorum (program yine de çıkmak üzereyse).
adrianm

1
asıl nokta, bir geliştiricinin, başvurunun yeniden gönderilmesiyle sonuçlanan tüm kod yollarına atmak için tüm çağrıları yazması gerektiğidir. SO örneğin bir Sözlük bir örnek koymak, sözlüğü girişleri sildiğimde dispose çağırmak zorunda. Bu durumda gerekli olmayan bir çok güçlük - nesnenin atılması
gerekmiyor

3
@ pm100 Re: Gereksiz yere IDisposable uygulamak - codeproject.com/KB/dotnet/idisposable.aspx adresinde bu konuda düşünmek isteyebileceğiniz bazı nadir durumları tartışan ayrıntılı bir makale var (çok nadir, eminim). Kısacası: İleride veya türetilmiş bir nesnede IDisposable gereksinimini öngörüyorsanız, bazı türetilmiş nesnelerin gerektirdiği "dilimleme" sorunlarından kaçınmak için ID sınıfını temel sınıfınızda "no-op" olarak uygulamayı düşünebilirsiniz. imha ve diğerleri yok.
Kevin P. Rice

4
  1. Yönetilmeyen kaynakları kullanan başka yönetilen nesneler kullanıyorsanız, bunların sonlandırılmasını sağlamak sizin sorumluluğunuzda değildir. Sizin sorumluluğunuz, nesneniz üzerinde Dispose çağrıldığında bu nesneler üzerinde Dispose çağrısı yapmaktır ve orada durur.

  2. Sınıfınız hiç kısıtlı kaynak kullanmıyorsa, sınıfınızı neden ID'yi kullanamaz hale getireceğinizi göremiyorum. Bunu yalnızca:

    • Yakında nesnelerinizde kıt kaynaklara sahip olacağınızı bilin, sadece şimdi değil (yani, "hala gelişiyoruz, bitmeden önce burada olacak", "sanırım buna ihtiyacımız olacağını düşünüyorum) ")
    • Kıt kaynakları kullanma
  3. Evet, kodunuzu kullanan kod, nesnenizin Dispose yöntemini çağırmalıdır. Ve evet, nesnenizi kullanan kod, usinggösterdiğiniz gibi kullanılabilir.

  4. (2 tekrar?) WebClient'in yönetilmeyen kaynakları veya IDisposable'ı uygulayan diğer yönetilen kaynakları kullanması muhtemeldir. Bununla birlikte, kesin neden önemli değildir. Önemli olan, IDisposable'ı gerçekleştirmesidir ve bu nedenle, WebClient'in başka hiçbir kaynak kullanmasa bile, nesneyi bitirdiğinizde imha ederek bu bilgiye göre hareket etmeniz size düşer.


4

Desen atın:

public abstract class DisposableObject : IDisposable
{
    public bool Disposed { get; private set;}      

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

    ~DisposableObject()
    {
        Dispose(false);
    }

    private void Dispose(bool disposing)
    {
        if (!Disposed)
        {
            if (disposing)
            {
                DisposeManagedResources();
            }

            DisposeUnmanagedResources();
            Disposed = true;
        }
    }

    protected virtual void DisposeManagedResources() { }
    protected virtual void DisposeUnmanagedResources() { }
}

Kalıtım örneği:

public class A : DisposableObject
{
    public Component components_a { get; set; }
    private IntPtr handle_a;

    protected override void DisposeManagedResources()
    {
        try
        {
          Console.WriteLine("A_DisposeManagedResources");
          components_a.Dispose();
          components_a = null;
        }
        finally
        { 
          base.DisposeManagedResources();
        }
    }

    protected override void DisposeUnmanagedResources()
    {
        try
        {
          Console.WriteLine("A_DisposeUnmanagedResources");
          CloseHandle(handle_a);
          handle_a = IntPtr.Zero;
        }
        finally
        { 
          base.DisposeUnmanagedResources();
        }
    }
}

public class B : A
{
    public Component components_b { get; set; }
    private IntPtr handle_b;

    protected override void DisposeManagedResources()
    {
        try
        {
          Console.WriteLine("B_DisposeManagedResources");
          components_b.Dispose();
          components_b = null;
        }
        finally
        { 
          base.DisposeManagedResources();
        }
    }

    protected override void DisposeUnmanagedResources()
    {
        try
        {
          Console.WriteLine("B_DisposeUnmanagedResources");
          CloseHandle(handle_b);
          handle_b = IntPtr.Zero;
        }
        finally
        { 
          base.DisposeUnmanagedResources();
        }
    }
}

4

Başka bir cevabın bazı yönleri 2 nedenden dolayı biraz yanlıştır:

İlk,

using(NoGateway objNoGateway = new NoGateway())

aslında:

try
{
    NoGateway = new NoGateway();
}
finally
{
    if(NoGateway != null)
    {
        NoGateway.Dispose();
    }
}

OutOfMemory istisnanız yoksa 'yeni' operatör asla 'null' döndürmemelidir, bu saçma gelebilir. Ancak aşağıdaki durumları göz önünde bulundurun: 1. IDisposable kaynağı döndüren bir FactoryClass veya 2'yi çağırırsınız. Uygulamasına bağlı olarak IDisposable'dan devralınabilen veya kalmayacak bir türünüz varsa - IDisposable deseninin yanlış uygulandığını gördüğümü unutmayın. geliştiricilerin IDisposable'dan (kötü, kötü, kötü) miras almadan sadece bir Dispose () yöntemi ekledikleri birçok istemcide. Bir mülk veya yöntemden döndürülen IDisposable bir kaynak da olabilir (yine kötü, kötü, kötü - IDisposable kaynaklarınızı vermeyin)

using(IDisposable objNoGateway = new NoGateway() as IDisposable)
{
    if (NoGateway != null)
    {
        ...

'As' operatörü null döndürürse (veya kaynağı döndüren özellik veya yöntem) ve 'using' bloğundaki kodunuz 'null' değerine karşı koruma sağlıyorsa, çünkü null bir nesneye Dispose çağrılmaya çalıştığınızda kodunuz patlamaz 'yerleşik' null kontrol.

Yanıtınızın doğru olmamasının ikinci nedeni, aşağıdaki stmt'den kaynaklanmaktadır:

GC'nin nesnenizi yok etmesi üzerine bir sonlandırıcı çağrılır

İlk olarak, sonuçlandırma (GC'nin kendisi gibi) deterministik değildir. CLR, ne zaman bir sonlandırıcı arayacağını belirler. yani geliştiricinin / kodun hiçbir fikri yoktur. IDisposable paterni doğru bir şekilde uygulanırsa (yukarıda gönderdiğim gibi) ve GC.SuppressFinalize () çağrılmışsa, Sonlandırıcı çağrılmaz. Deseni doğru bir şekilde uygulamak için büyük nedenlerden biri budur. Yönetilen işlem başına yalnızca 1 Finalizer iş parçacığı olduğundan, mantıksal işlemci sayısından bağımsız olarak, GC.SuppressFinalize () öğesini çağırmayı unutarak Finalizer iş parçasını yedekleyerek veya hatta asarak performansı kolayca düşürebilirsiniz.

Blogumda Atma Deseni'nin doğru bir uygulamasını yayınladım: Atma Deseni Nasıl Düzgün Uygulanır


2
Yazmaktan NoGateway = new NoGateway();ve emin misin NoGateway != null?
Cur


@DaveInCaz doğru gibi görünüyor. Hiçbir yerde 'Icey'i görmüyorum ama cevabımın bağlamı yukarıdaki bağlantınızın verdiği cevaba yönlendirilmiş gibi görünüyor. Belki kullanıcı adını değiştirdi?
Dave Black

@DaveBlack cool, teşekkürler. Bunu metinde düzenledim.
UuDdLrLrSs

2

1) WebClient yönetilen bir tiptir, bu nedenle bir sonlandırıcıya ihtiyacınız yoktur. Kullanıcılarınızın NoGateway sınıfınızın Dispose () yöntemini kullanmaması ve yerel türün (GC tarafından toplanmayan) daha sonra temizlenmesi gerekiyorsa, sonlandırıcı gerekir. Bu durumda, kullanıcı Dispose () öğesini çağırmazsa, içerdiği WebClient, NoGateway yaptıktan hemen sonra GC tarafından atılır.

2) Dolaylı olarak evet, ama endişelenmenize gerek yok. Kodunuz standart olarak doğrudur ve kullanıcılarınızın Dispose () işlevini kolayca unutmasını engelleyemezsiniz.


2

Msdn'den desen

public class BaseResource: IDisposable
{
   private IntPtr handle;
   private Component Components;
   private bool disposed = false;
   public BaseResource()
   {
   }
   public void Dispose()
   {
      Dispose(true);      
      GC.SuppressFinalize(this);
   }
   protected virtual void Dispose(bool disposing)
   {
      if(!this.disposed)
      {        
         if(disposing)
         {
            Components.Dispose();
         }         
         CloseHandle(handle);
         handle = IntPtr.Zero;
       }
      disposed = true;         
   }
   ~BaseResource()      
   {      Dispose(false);
   }
   public void DoSomething()
   {
      if(this.disposed)
      {
         throw new ObjectDisposedException();
      }
   }
}
public class MyResourceWrapper: BaseResource
{
   private ManagedResource addedManaged;
   private NativeResource addedNative;
   private bool disposed = false;
   public MyResourceWrapper()
   {
   }
   protected override void Dispose(bool disposing)
   {
      if(!this.disposed)
      {
         try
         {
            if(disposing)
            {             
               addedManaged.Dispose();         
            }
            CloseHandle(addedNative);
            this.disposed = true;
         }
         finally
         {
            base.Dispose(disposing);
         }
      }
   }
}

1
using(NoGateway objNoGateway = new NoGateway())

eşittir

try
{
    NoGateway = new NoGateway();
}

finally
{
    NoGateway.Dispose();
}

GC'ye nesnenizi yok eden bir sonlandırıcı çağrılır. Bu, yönteminizi bıraktığınızdan tamamen farklı bir zamanda olabilir. IDisposable imhası, kullanım bloğundan çıktıktan hemen sonra çağrılır. Bu nedenle, desen genellikle artık ihtiyacınız olmadığında kaynakların serbest bırakılması için kullanılır.


1
GC'yi nesneyi yok eden bir sonlandırıcı çağrılmaz. "Sonlandır" geçersiz kılınırsa, GC başka şekilde nesneyi yok ederse, sonlandırılması gereken bir nesne kuyruğuna yerleştirilir, geçici olarak ona güçlü bir referans oluşturur ve - en azından geçici olarak - "dirilir".
supercat

-5

Bildiğim kadarıyla, Finalizer / Destructor'ı kullanmamanız şiddetle tavsiye edilir:

public ~MyClass() {
  //dont use this
}

Çoğunlukla, bunun ne zaman veya IF olarak adlandırılacağını bilmemesinden kaynaklanır. Dispose yöntemi, özellikle doğrudan kullanıyorsanız veya attığınız takdirde çok daha iyidir.

kullanmak iyidir. kullan :)


2
Coco'nun cevabındaki linki takip etmelisin. Evet, / Dispose kullanmak daha iyidir, ancak Disposable sınıfı kesinlikle her ikisini de uygulamalıdır.
Henk Holterman

İlginçtir, microsoft'tan okuduğum tüm dokümanlar - örneğin çerçeve tasarımı yönergeleri - bir yıkıcı kullanmadığını söylüyor. Her zaman IDisposable kullanın.
Nic Wise

5
Sadece sınıf kullanma ve yazma arasındaki farkı ayırt et , tekrar okuyun.
Henk Holterman

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.