Soru IDisposable'ın tüm sınıflarınıza yayılmasını nasıl önlersiniz?


Bu basit sınıflarla başlayın ...

Bunun gibi basit bir dizi dersim olduğunu varsayalım:

class Bus
{
    Driver busDriver = new Driver();
}

class Driver
{
    Shoe[] shoes = { new Shoe(), new Shoe() };
}

class Shoe
{
    Shoelace lace = new Shoelace();
}

class Shoelace
{
    bool tied = false;
}

bir Bus bir var Driver, Driver iki tane var Shoeher biri Shoe bir var Shoelace. Hepsi çok aptalca.

Shoelace için bir IDE bir nesne ekle

Daha sonra bazı operasyonlara karar verdim Shoelace çok iş parçacıklı olabilir, bu yüzden EventWaitHandle Konu ile iletişim kurmak için. Yani Shoelace şimdi böyle görünüyor:

class Shoelace
{
    private AutoResetEvent waitHandle = new AutoResetEvent(false);
    bool tied = false;
    // ... other stuff ..
}

Ayakkabı Bayrağına Uygulanabilir

Ama şimdi Microsoft'un FxCop'u şikayet edecek: "Aşağıdaki" Kullanılabilir "türlerin üyeleri oluşturduğu için 'Shoelace' üzerinde tanımlanmalıdır: 'EventWaitHandle'.

Tamam, uygularım IDisposable üzerinde Shoelace ve benim küçük küçük sınıfım bu korkunç dağınıklık olur:

class Shoelace : IDisposable
{
    private AutoResetEvent waitHandle = new AutoResetEvent(false);
    bool tied = false;
    private bool disposed = false;

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

    ~Shoelace()
    {
        Dispose(false);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                if (waitHandle != null)
                {
                    waitHandle.Close();
                    waitHandle = null;
                }
            }
            // No unmanaged resources to release otherwise they'd go here.
        }
        disposed = true;
    }
}

Ya da (yorumcular tarafından belirtildiği gibi) Shoelace kendisi yönetilmeyen bir kaynağa sahip değildir, daha basit imha uygulamasına gerek duymadan kullanabilirim Dispose(bool) ve Yıkıcı:

class Shoelace : IDisposable
{
    private AutoResetEvent waitHandle = new AutoResetEvent(false);
    bool tied = false;

    public void Dispose()
    {
        if (waitHandle != null)
        {
            waitHandle.Close();
            waitHandle = null;
        }
        GC.SuppressFinalize(this);
    }
}

IDisposable spreads olarak korku izle

Doğru bu doğru. Ama şimdi FxCop bundan şikayet edecek Shoe bir oluşturur Shoelace, yani Shoe olmalıdır IDisposable çok.

Ve Driver yaratır Shoe yani Driver olmalıdır IDisposable. Ve Bus yaratır Driver yani Bus olmalıdır IDisposable ve bunun gibi.

Aniden benim küçük değişim Shoelace bana çok iş yapıyor ve patronum neden ödeme yapmam gerektiğini merak ediyor Bus bir değişiklik yapmak Shoelace.

Soru

Bu yayılmayı nasıl önlersiniz IDisposableama yine de yönetilmeyen nesnelerin uygun şekilde bertaraf edilmesini sağlıyor musunuz?


133
2018-03-19 11:13


Menşei


Son derece iyi bir soru, cevabın kullanımlarını en aza indirdiğine inanıyorum ve yüksek seviyeli IDisposables'ları kullanımla birlikte kısa süreliğine tutmaya çalışıyorum, ancak bu her zaman mümkün değildir (özellikle bu kimlik öğelerinin C ++ dll veya benzerleriyle birlikte çalıştığı durumlarda). Cevaplara bakın. - annakata
Evet, anladığım kadarıyla yayılmayı anladım ana sorun ... ama örnek kodunuzu değiştiremiyorum. :-) - Ðаn
Tamam Dan, Sorunu, Ayakkabı Üzerinizde her iki yöntemle de uygulanabileceğini gösterme konusunda güncelledim. - GrahamS
IDisposable.Dispose () için belgeler, "Bir nesnenin Dispose yöntemi birden çok çağrılırsa, nesne ilk çağrının ardından tüm çağrıları görmezden gelmelidir. Dispose yöntemi, birden çok kez çağrılırsa, nesne bir istisna atmamalıdır." bu yüzden waitHandle.Dispose () işlevini "null" işaretiyle çağırın. - Ðаn
Ne olursa olsun, aslında "Sonlandırıcı (eksi sonlandırıcı)" tam desen olduğu için, "Ayakkabının Üzerinde Kullanılabilir Uygula" bölümünde gösterildiği gibi, hala Dispose (bool) yöntemini kullanmalısınız. Çünkü bir sınıf çarpıtmaları, tek kullanımlık bir finalizer gerektirdiği anlamına gelmez. - Scott Dorman


Cevaplar:


Gerçekten "önleme" yi kullanamazsınız. Bazı sınıflar gibi tasfiye edilmesi gerekir AutoResetEventve en verimli yolu bunu Dispose() Sonlaştırıcılar üstünden kaçınmak için yöntem. Fakat bu yöntem bir şekilde çağrılmalıdır, tıpkı sizin örneğinizde olduğu gibi, kimliğini saklayabilen veya içerdiği sınıfların bunları bertaraf etmesi gerektiği gibi, bu yüzden de tek kullanımlık olmalıdırlar. Bundan kaçınmanın tek yolu şudur:

  • Mümkün olduğunda IDisposable sınıflarını kullanmaktan kaçının, tek bir yerde olayları kilitleyin veya bekleyin, pahalı kaynakları tek bir yerde tutun, vb.
  • Onları sadece ihtiyaç duyduğunuzda yaratın ve hemen ardından ( using Desen)

Bazı durumlarda, isteğe bağlı bir durumu desteklediği için, tek kullanımlık olarak göz ardı edilebilir. Örneğin, WaitHandle adlandırılmış Mutex'i desteklemek için IDisposable kullanır. Bir ad kullanılmıyorsa, Dispose yöntemi hiçbir şey yapmaz. MemoryStream başka bir örnektir, sistem kaynaklarını kullanmaz ve Tasfiye uygulaması da hiçbir şey yapmaz. Yönetilmeyen bir kaynağın kullanılıp kullanılmadığına dikkat edilmesi öğretici olabilir. Bu yüzden .net kütüphaneleri için mevcut kaynakları inceleyebilir veya bir decompiler kullanabilir.


34
2018-03-19 11:21



Uygulamada bir şey yapmadığı için bunu görmezden gelebileceğinize dair tavsiyeler kötüdür, çünkü gelecekteki bir sürüm aslında Dispose'da önemli bir şey yapabilir ve artık sızıntıyı izlemek zor. - Andy
"Pahalı kaynakları koruyun", IDisposable'lar mutlaka pahalı değil, hatta bir bekleme beklemesi kullanan bu örnek aldığınız gibi "hafif" hakkındadır. - markmnl


Doğruluk açısından, eğer bir ana nesne, şimdi tek kullanımlık olması gereken bir çocuk nesnesine sahipse ve esasen bir nesneye sahipse, bir nesne ilişkisinden IDisposable'ın yayılmasını önleyemezsiniz. FxCop bu durumda doğrudur ve ebeveynin IDisposable olması gerekir.

Yapabilecekleriniz, nesne hiyerarşinizdeki bir yaprak sınıfına bir IDisposable eklemekten kaçınmaktır. Bu her zaman kolay bir iş değildir, ancak ilginç bir alıştırmadır. Mantıksal bir bakış açısından, bir ShoeLace'ın tek kullanımlık olması için bir neden yoktur. Burada bir WaitHandle eklemek yerine, kullanıldığı yerde bir ShoeLace ve WaitHandle arasında bir ilişki eklemek de mümkündür. En basit yol bir Sözlük örneğidir.

WaitHandle'ı WaitHandle'ın kullanıldığı noktadaki bir harita üzerinden gevşek bir ilişkiye taşıyabiliyorsanız, bu zinciri kırabilirsiniz.


19
2018-03-19 12:23



Orada bir dernek kurmak çok garip geliyor. Bu AutoResetEvent, Shoelace uygulamasına özeldir, bu yüzden herkese açık olarak açığa vurmak bence yanlış olur. - GrahamS
@GrahamS, söylemeliyim ki bunu açıkça ortaya çıkarmayın. Ayakkabı bağlarının bağlandığı noktaya taşınabileceğini söylüyorum. Belki de ayakkabı bağcıklarını bağlarsak, karmaşık bir görev onlar için ayakkabı bağı sınıfları olmalıdır. - JaredPar
Cevabınızı bazı kod parçacıkları JaredPar ile genişletir misiniz? Örneğimi biraz esnetiyor olabilirim ama Shoelace'ın sabırla beklediğim Tyer iş parçacığı oluşturup beklediğini tahmin ediyorum. WaitHandle.WaitOne () Ayakkabı bağı, ipliğin bağlanmaya başlamasını istediğinde waitHandle.Set () çağırırdı. - GrahamS
Bu konuda +1. Bence sadece Ayakkabı Bağı eşzamanlı olarak çağrılacak ama diğerlerini değil. Toplam kökün ne olması gerektiğini düşün. Bu durumda, etki alanına aşina olmamasına rağmen, Bus, IMHO olmalı. Yani, bu durumda otobüs, otobüste beklemeyi ve tüm işlemleri içermeli ve tüm çocukları senkronize edilecektir. - Johannes Gustafsson


Önlemek IDisposable Yayılmadan, tek bir yöntemin içinde tek kullanımlık bir nesnenin kullanımını kapsamaya çalışmalısınız. Tasarlamaya çalışın Shoelace farklı:

class Shoelace { 
  bool tied = false; 

  public void Tie() {

    using (var waitHandle = new AutoResetEvent(false)) {

      // you can even pass the disposable to other methods
      OtherMethod(waitHandle);

      // or hold it in a field (but FxCop will complain that your class is not disposable),
      // as long as you take control of its lifecycle
      _waitHandle = waitHandle;
      OtherMethodThatUsesTheWaitHandleFromTheField();

    } 

  }
} 

Bekleme kolu kapsamı ile sınırlıdır Tieyöntem ve sınıfın tek kullanımlık bir alana sahip olması gerekmemektedir ve bu nedenle tek kullanımlık olması gerekmeyecektir.

Bekleme kolu içinde bir uygulama detayı olduğundan ShoelaceBu, herhangi bir şekilde kamu arayüzünde, beyanında yeni bir arayüz eklemek gibi değişmemelidir. Artık tek kullanımlık alana ihtiyaç duymadığınızda ne olacak? IDisposable beyan? Hakkında düşünürseniz Shoelace  soyutlamaAltyapı bağımlılıkları tarafından kirletilmemesi gerektiğini, IDisposable. IDisposable soyutlama, deterministik temizlik için çağıran bir kaynağı kapsülleyen sınıflara ayrılmalıdır; yani, kullanılıp atılabilirliğin bir parçası olduğu sınıflar için soyutlama.


14
2018-06-09 12:48



Shoelace'in uygulama detayının kamu arayüzünü kirletmemesi konusunda tamamen katılıyorum, ama benim amacım, kaçınılması zor olabilir. Burada önerdiğiniz her zaman mümkün olmaz: AutoResetEvent () 'in amacı iş parçacıkları arasında iletişim kurmaktır, dolayısıyla kapsamı genellikle tek bir yöntemin ötesine uzanır. - GrahamS
@GrahamS: Bu yüzden bu şekilde tasarlamayı denedim. Tek kullanımlık malzemeyi diğer yöntemlere de iletebilirsiniz. Sadece sınıfa yapılan harici çağrılar tek kullanımlık yaşam döngüsünü kontrol ettiğinde bozulur. Cevabı uygun şekilde güncelleyeceğim. - Jordão
Üzgünüm, tek kullanımlık ürünü geçebileceğini biliyorum, ama hala çalışmıyorum. Benim örneğimde AutoResetEvent Aynı sınıf içinde çalışan farklı iş parçacıkları arasında iletişim kurmak için kullanılır, bu yüzden bir üye değişkeni olmak zorundadır. Kapsamını bir yöntemle sınırlayamazsınız. (ör. bir iş parçacığının sadece bir iş için beklemeyi beklediğini düşünün. waitHandle.WaitOne(). Ana iş parçacığı daha sonra shoelace.Tie() sadece bir yöntem yapar waitHandle.Set() ve hemen döner). - GrahamS


Bu, "hızlı düzeltme" nin bir bataklığa dönüştüğü zaman olduğu gibi, daha yüksek seviyeli bir tasarım sorununa çok benziyor. Çıkış yolları hakkında daha fazla bilgi için, bulabilirsiniz bu konu faydalı.


3
2018-03-19 12:30



AutoResetEvent sadece bir örnektir. İplik görünümüne bir çözüm aramıyorum çünkü Ayakkabı Parçası herhangi bir üyesi IDisposable oluşturuyorsa aynı sorun oluşur. - GrahamS


İlginç eğer Driver yukarıdaki gibi tanımlanmıştır:

class Driver
{
    Shoe[] shoes = { new Shoe(), new Shoe() };
}

Sonra ne zaman Shoe yapılmış IDisposable, FxCop (v1.36) bunu şikayet etmez Driver ayrıca olmalı IDisposable.

Ancak böyle tanımlanırsa:

class Driver
{
    Shoe leftShoe = new Shoe();
    Shoe rightShoe = new Shoe();
}

o zaman şikayet eder.

İlk versiyonda çünkü bunun bir çözüm yerine FxCop'un bir sınırlaması olduğunu sanıyorum. Shoe örnekler hala tarafından oluşturulmaktadır Driver ve yine de bir şekilde bertaraf edilmeli.


3
2018-03-19 13:39



Gösteri, Tek Başına Uygulanabilir olduğunda, bir alan başlatıcısında oluşturmak tehlikelidir. FXCop'un böyle başlatmalara izin vermemesi ilginçtir, çünkü C # 'de onları güvenli bir şekilde yapmanın tek yolu ThreadStatic değişkenleridir (aşağı doğru gizlidir). Vb.net'de, IdentStatic değişkenleri olmadan IDisposable nesneleri güvenli bir şekilde başlatmak mümkündür. Kod hala çirkin, ama pek de çirkin değil. MS, bu tür alan başlatıcılarını güvenli bir şekilde kullanmanın güzel bir yolunu sunabilseydi. - supercat
@supercat: teşekkürler. Açıklayabilir misin niye ya tehlikeli? Sanırım bir istisnadan biri içine atılmışsa Shoe daha sonra kurucular shoes zor bir durumda kalacaktı? - GrahamS
Belirli bir tür tek kullanımlık malzemenin güvenli bir şekilde terk edilebileceğini bilemezse, yaratılan her nesnenin Tasfiye Edilmesini sağlar. Bir nesnenin alan başlatıcısında bir IDisposable yaratılır, ancak nesne tamamen oluşturulmadan önce bir istisna atılırsa, nesne ve tüm alanları terk edilir. Nesnenin inşası, bu inşaatın başarısız olduğunu tespit etmek için bir "try" bloğu kullanan bir fabrika yöntemine sarılı olsa bile, fabrikanın imha edilmeye ihtiyaç duyan nesnelere referans vermesi zordur. - supercat
C # ile başa çıkmayı bildiğim tek yol, mevcut kurucu atıyorsa atma ihtiyacı olacak nesnelerin bir listesini tutmak için bir ThreadStatic değişkeni kullanmak olacaktır. Daha sonra, her alan başlatıcısı, oluşturulduğu gibi her bir nesneyi kaydettirir, fabrika başarılı bir şekilde tamamlarsa listeyi temizler ve temizlemediyse listeden tüm öğeleri atın. Bu uygulanabilir bir yaklaşım olurdu, ancak ThreadStatic değişkenler çirkin. Vb.net'de, herhangi bir ThreadStatic değişkenine gerek duymadan benzer bir teknik kullanılabilir. - supercat


Tasarımınızı çok sıkı bir şekilde tutarsanız, kullanımınızı yaymaktan kaçınmanın teknik bir yolu olduğunu düşünmüyorum. Biri tasarımın doğru olup olmadığını merak etmeli.

Örneğinizde, sanırım ayakkabının ayakkabı bağına sahip olması mantıklı ve belki de şoförün kendi ayakkabılarına sahip olması gerekiyor. Ancak otobüs, sürücüye sahip olmamalıdır. Tipik olarak, otobüs sürücüleri hurdaya giden otobüsleri takip etmezler :) Şoförler ve ayakkabılar durumunda, sürücüler nadiren kendi ayakkabılarını yaparlar, yani onlar gerçekten "sahip olmadıkları" anlamına gelir.

Alternatif bir tasarım şöyle olabilir:

class Bus
{
   IDriver busDriver = null;
   public void SetDriver(IDriver d) { busDriver = d; }
}

class Driver : IDriver
{
   IShoePair shoes = null;
   public void PutShoesOn(IShoePair p) { shoes = p; }
}

class ShoePairWithDisposableLaces : IShoePair, IDisposable
{
   Shoelace lace = new Shoelace();
}

class Shoelace : IDisposable
{
   ...
}

Yeni tasarım, ne yazık ki daha karmaşıktır, çünkü ayakkabı ve sürücülerin somut örneklerini somutlaştırmak ve atmak için ekstra sınıflar gerektirir, ancak bu komplikasyon çözülmekte olan sorunun doğasında bulunur. İyi olan şey, otobüslerin artık ayakkabı bağlarının atılması amacıyla artık tek kullanımlık olması gerekmemesidir.


3
2018-01-06 10:39



Bu aslında orijinal sorunu çözmez. FxCop'u sessiz tutabilir, ancak bir şey hala Ayakkabı Bağı'nı atmalıdır. - AndrewS
Yaptığın her şeyin sorunu başka bir yere taşıması gerektiğini düşünüyorum. ShoePairWithDisposableLaces şimdi Shoelace () sahibi, bu yüzden aynı zamanda IDisposable yapılmalıdır - böylece ayakkabı kim atıyor? Bunu halledecek bir IoC konteynerine mi bırakıyorsun? - GrahamS
@AndrewS: Gerçekten de, bir şey sürücüler, ayakkabılar ve bağcıkları atmak zorundadır, ancak imha otobüslerle başlatılmamalıdır. @GrahamS: Haklısın, sorunu başka bir yere taşıyorum çünkü başka bir yere ait olduğuna inanıyorum. Tipik olarak, ayakkabıların atılmasından da sorumlu olacak bir sınıf ayakkabılar olacaktır. Ben de ShoePairWithDisposableLaces tek kullanımlık yapmak için kod düzenledim. - Joh
@Joh: Teşekkürler. Söylediğiniz gibi, başka bir yere taşımakla ilgili sorun, daha çok karmaşıklık yaratmanızdır. ShoePairWithDisposableLaces başka bir sınıf tarafından oluşturulmuşsa, o zaman Sürücü o zaman onun Ayakkabı ile bittiğinde, o sınıf uygun değil mi? - GrahamS
@Graham: evet, bir çeşit bildirim mekanizması gerekli olacaktı. Örneğin referans sayılarına dayalı bir elden çıkarma politikası kullanılabilir. Eklenmiş bir karmaşıklık var, ama muhtemelen bildiğiniz gibi, "Ücretsiz bir ayakkabı diye bir şey yok" :) - Joh


Temel olarak, Kompozisyon veya A sınıfı birleştirme ile sınıfları birleştirdiğinizde ne olur. Bahsedildiği gibi, ilk çıkış, bekçi kulübesini ayakkabı bağından çıkarmaktır.

Bunu söyledikten sonra, yönetilmeyen kaynaklarınız olmadığında Tek kullanımlık modeli önemli ölçüde kapatabilirsiniz. (Bunun için resmi bir referans arıyorum.)

Ama yok ediciyi ve GC.SuppressFinalize'yi (bu) atlayabilirsiniz; ve belki de sanal boşluğu bir kenara bırakın (boşaltarak).


2
2018-03-19 11:55



Teşekkürler Henk. O zaman ayakkabı bağı dışında waitHandle yaratılsaydım birisi hala bir yere atmak zorunda kalsın bu yüzden nasıl olur birisi Ayakkabı Bağı'nın onunla bittiğini (ve Ayakkabı Bağı'nın başka bir sınıfa geçmediğini) biliyor musunuz? - GrahamS
Sadece Yaratılış'ı değil, aynı zamanda '' beklemede '' sorumluluğunu da taşımalısınız. jaredPar'ın cevabı, bir fikrin ilginç bir başlangıcı vardır. - Henk Holterman
Bu blog, tek kullanımlık bir şekilde, yönetilen ve yönetilmeyen kaynaklardan kaçınarak görevi sadeleştirmeyi hedefler. blog.stephencleary.com/2009/08/... - PaulS


Kontrolün İnversiyonunu Kullanmaya Ne Dersiniz?

class Bus
{
    private Driver busDriver;

    public Bus(Driver busDriver)
    {
        this.busDriver = busDriver;
    }
}

class Driver
{
    private Shoe[] shoes;

    public Driver(Shoe[] shoes)
    {
        this.shoes = shoes;
    }
}

class Shoe
{
    private Shoelace lace;

    public Shoe(Shoelace lace)
    {
        this.lace = lace;
    }
}

class Shoelace
{
    bool tied;
    private AutoResetEvent waitHandle;

    public Shoelace(bool tied, AutoResetEvent waitHandle)
    {
        this.tied = tied;
        this.waitHandle = waitHandle;
    }
}

class Program
{
    static void Main(string[] args)
    {
        using (var leftShoeWaitHandle = new AutoResetEvent(false))
        using (var rightShoeWaitHandle = new AutoResetEvent(false))
        {
            var bus = new Bus(new Driver(new[] {new Shoe(new Shoelace(false, leftShoeWaitHandle)),new Shoe(new Shoelace(false, rightShoeWaitHandle))}));
        }
    }
}

1
2017-07-02 11:34



Artık bunu arayanların sorunu haline getirdiniz ve Ayakkabı bağı, ihtiyaç duyulduğunda kullanılabilen bekleme koluna bağlı olamaz. - Andy
@andy "Ayakkabı bağı, ihtiyaç duyulduğunda hazır bekleyen bekleyişe bağlı olamaz" dan ne demek istiyorsun? Kurucuya geçti. - Darragh
Başka bir şey Ayakkabıcıya vermeden önce otoriter durumlara bulaşmış olabilir ve kötü bir durumda ARE ile başlayabilir; Başka bir şey de ARE'nin devletiyle uğraşırken, Ayakkabı Bağı'nın yaptığı şey ise Ayakkabı Sahibinin yanlış şeyi yapmasına neden oluyor. Aynı sebep sadece özel üyelere kilitleniyor. "Genel olarak, kod kontrollerinizin ötesinde örnekleri kilitlemekten kaçının" docs.microsoft.com/en-us/dotnet/csharp/language-reference/... - Andy
Sadece özel üyelere kilitlenmeye katılıyorum. Ama beklemek kolları farklı gibi görünüyor. Aslında, aynı örneklemin nesneler arasında iletişim kurmak için kullanılması gerektiğinden, nesneye bunları iletmek daha kullanışlı görünmektedir. Yine de, IoC'nin OP'nin problemine faydalı bir çözüm getirdiğini düşünüyorum. - Darragh
OPs örneğinin waithandle'ı özel bir üye olarak verdiği göz önüne alındığında, güvenli varsayım, nesnenin kendisine özel erişimi beklemesidir. Kolu örnek dışında görünmesini sağlamak bunu ihlal eder. Tipik olarak bir IoC böyle şeyler için iyi olabilir, ancak iş parçacığına geldiğinde değil. - Andy