C # 'ta olay işleyicilerini açıkça kaldırmak gerekli midir?


120

Birkaç etkinlik sunan bir sınıfım var. Bu sınıf küresel olarak bildirilir, ancak bu küresel bildirim üzerine örneklenmez - ihtiyaç duyan yöntemlerde ihtiyaç duyulan temelde örneklenir.

Bir yöntemde bu sınıfa her ihtiyaç duyulduğunda, örneklenir ve olay işleyicileri kaydedilir. Yöntem kapsam dışına çıkmadan önce olay işleyicilerini açıkça kaldırmak gerekli midir?

Yöntem kapsam dışına çıktığında, sınıfın örneği de gider. Kapsam dışına çıkan bu örneğe kayıtlı bırakılan olay işleyicilerin bellek ayak izi etkisi var mı? (Olay işleyicisinin, GC'nin sınıf örneğini artık başvurulmadığını görmesini engelleyip engellemediğini merak ediyorum.)

Yanıtlar:


184

Senin durumunda her şey yolunda. Olay işleyicilerinin hedeflerini canlı tutan olayları yayınlayan nesnedir . Yani eğer sahipsem:

publisher.SomeEvent += target.DoSomething;

o publisherzaman bir referansı vardır, targetancak tersi yoktur.

Sizin durumunuzda, yayıncı çöp toplama için uygun olacaktır (buna başka referans olmadığı varsayılarak), bu nedenle olay işleyici hedeflerine bir referans olması gerçeği alakasızdır.

Zor durum, yayıncının uzun ömürlü olduğu ancak abonelerin olmak istemediği durumdur - bu durumda işleyicileri aboneliğinizi iptal etmeniz gerekir. Örneğin, bant genişliği değişiklikleri hakkında eşzamansız bildirimlere abone olmanıza izin veren bir veri aktarım hizmetiniz olduğunu ve aktarım hizmeti nesnesinin uzun ömürlü olduğunu varsayalım. Bunu yaparsak:

BandwidthUI ui = new BandwidthUI();
transferService.BandwidthChanged += ui.HandleBandwidthChange;
// Suppose this blocks until the transfer is complete
transferService.Transfer(source, destination);
// We now have to unsusbcribe from the event
transferService.BandwidthChanged -= ui.HandleBandwidthChange;

(Aslında olay işleyicisini sızdırmadığınızdan emin olmak için bir nihayet bloğu kullanmak istersiniz.) Eğer abonelikten çıkmazsak, o zaman BandwidthUIen azından transfer hizmeti kadar yaşar.

Şahsen buna nadiren rastlarım - genellikle bir etkinliğe abone olursam, o etkinliğin hedefi en azından yayıncı kadar yaşar - bir form, örneğin üzerindeki düğme kadar dayanır. Bu potansiyel konu hakkında bilgi sahibi olmaya değer, ancak bence bazı insanlar ihtiyaç duymadıkları zaman endişeleniyor çünkü referansların hangi yöne gittiğini bilmiyorlar.

DÜZENLEME: Bu, Jonathan Dickinson'un yorumuna cevap vermek içindir. İlk olarak, eşitlik davranışını açıkça veren Delegate.Equals (nesne) belgelerine bakın .

İkinci olarak, işte abonelik iptalinin çalıştığını gösteren kısa ama eksiksiz bir program:

using System;

public class Publisher
{
    public event EventHandler Foo;

    public void RaiseFoo()
    {
        Console.WriteLine("Raising Foo");
        EventHandler handler = Foo;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
        else
        {
            Console.WriteLine("No handlers");
        }
    }
}

public class Subscriber
{
    public void FooHandler(object sender, EventArgs e)
    {
        Console.WriteLine("Subscriber.FooHandler()");
    }
}

public class Test
{
    static void Main()
    {
         Publisher publisher = new Publisher();
         Subscriber subscriber = new Subscriber();
         publisher.Foo += subscriber.FooHandler;
         publisher.RaiseFoo();
         publisher.Foo -= subscriber.FooHandler;
         publisher.RaiseFoo();
    }
}

Sonuçlar:

Raising Foo
Subscriber.FooHandler()
Raising Foo
No handlers

(Mono ve .NET 3.5SP1'de test edilmiştir.)

Daha fazla düzenleme:

Bu, bir aboneye yönelik referanslar varken bir etkinlik yayıncısının toplanabileceğini kanıtlamak içindir.

using System;

public class Publisher
{
    ~Publisher()
    {
        Console.WriteLine("~Publisher");
        Console.WriteLine("Foo==null ? {0}", Foo == null);
    }

    public event EventHandler Foo;
}

public class Subscriber
{
    ~Subscriber()
    {
        Console.WriteLine("~Subscriber");
    }

    public void FooHandler(object sender, EventArgs e) {}
}

public class Test
{
    static void Main()
    {
         Publisher publisher = new Publisher();
         Subscriber subscriber = new Subscriber();
         publisher.Foo += subscriber.FooHandler;

         Console.WriteLine("No more refs to publisher, "
             + "but subscriber is alive");
         GC.Collect();
         GC.WaitForPendingFinalizers();         

         Console.WriteLine("End of Main method. Subscriber is about to "
             + "become eligible for collection");
         GC.KeepAlive(subscriber);
    }
}

Sonuçlar (.NET 3.5SP1'de; Mono burada biraz tuhaf davranıyor gibi görünüyor. Bunu bir süre araştıracak):

No more refs to publisher, but subscriber is alive
~Publisher
Foo==null ? False
End of Main method. Subscriber is about to become eligible for collection
~Subscriber

2
Buna katılıyorum ama mümkünse kısaca "ama aboneler olmak istemiyorlar" derken neyi kastettiğinizi kısaca açıklayabilir veya tercihen örnek verebilir misiniz?
Peter McG

@Jon: Çok takdir ediyorum, bu yaygın değil ama dediğiniz gibi insanların bu konuda gereksiz yere endişelendiğini gördüm.
Peter McG

- = Çalışmıyor. - = Yeni bir temsilci ile sonuçlanır ve delegeler hedef yöntemi kullanarak eşitliği kontrol etmez, temsilci üzerinde bir nesne yapar.ReferenceEquals (). Listede yeni temsilci yok: hiçbir etkisi yok (ve yeterince tuhaf bir şekilde hata vermiyor).
Jonathan C Dickinson

2
@Jonathan: Hayır, delegeler hedef yöntemi kullanarak eşitliği kontrol ediyor. Bir düzenlemede kanıtlayacak.
Jon Skeet

Kabul ediyorum. İsimsiz delegelerle kafam karıştı.
Jonathan C Dickinson

8

Senin durumunda iyisin. Aslında sorunuzu tersten okudum, bir abonenin kapsam dışına çıktığını, yayıncının değil . Etkinlik yayıncısı kapsam dışına çıkarsa , aboneye yapılan referanslar (tabii ki abonenin kendisine değil!) Onunla birlikte gider ve bunları açıkça kaldırmaya gerek yoktur.

Orijinal cevabım, bir etkinlik abonesi oluşturup abonelikten çıkmadan kapsam dışına çıkmasına izin verirseniz ne olacağıyla ilgili aşağıda . Sorunuz için geçerli değil ama onu tarih için yerinde bırakacağım.

Sınıf hala olay işleyicileri aracılığıyla kayıtlıysa, yine de erişilebilir durumdadır. Hala canlı bir nesnedir. Bir olay grafiğini takip eden bir GC, onu bağlı bulacaktır. Evet, olay işleyicilerini açıkça kaldırmak isteyeceksiniz.

Nesnenin orijinal tahsisinin kapsamının dışında olması, onun GC için aday olduğu anlamına gelmez. Canlı referans kaldığı sürece yayındadır.


1
GC başvuruları görür - Hiç aboneliği iptal burada gerekli olduğuna inanmıyorum gelen değil kendisine, olay yayıncı ve biz burada endişe ediyoruz yayıncı var.
Jon Skeet

@Jon Skeet: Haklısın. Soruyu tersten okudum. Cevabımı gerçeği yansıtacak şekilde düzelttim.
Eddie
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.