web-dev-qa-db-de.com

Ist es notwendig, Ereignishandler in C # explizit zu entfernen?

Ich habe eine Klasse, die einige Veranstaltungen anbietet. Diese Klasse wird global deklariert, aber nicht in dieser globalen Deklaration instanziiert - sie wird nach Bedarf in den Methoden instanziiert, die sie benötigen.

Jedes Mal, wenn diese Klasse in einer Methode benötigt wird, wird sie instanziiert und Event-Handler werden registriert. Müssen die Ereignishandler explizit entfernt werden, bevor die Methode den Gültigkeitsbereich verlässt?

Wenn die Methode den Gültigkeitsbereich verlässt, verlässt auch die Instanz der Klasse den Gültigkeitsbereich. Hat das Verlassen von Ereignishandlern, die für diese Instanz registriert sind, eine Auswirkung auf den Speicherbedarf? (Ich frage mich, ob die Ereignisbehandlungsroutine den GC davon abhält, die Klasseninstanz als nicht mehr referenziert anzusehen.)

113
rp.

In deinem Fall ist alles in Ordnung. Es ist das Objekt, dasveröffentlichtdie Ereignisse, dieZieleder Ereignishandler am Leben erhalten. Also wenn ich habe:

publisher.SomeEvent += target.DoSomething;

dann hat publisher einen Verweis auf target, aber nicht umgekehrt.

In Ihrem Fall ist der Publisher für die Garbage Collection berechtigt (vorausgesetzt, es gibt keine weiteren Verweise darauf), sodass die Tatsache, dass es einen Verweis auf die Event-Handler-Ziele gibt, irrelevant ist.

Der schwierige Fall ist, wenn der Publisher eine lange Lebensdauer hat, die Abonnenten jedoch nicht möchten, dassder Handler abgemeldet werden muss. Angenommen, Sie verfügen über einen Datenübertragungsdienst, mit dem Sie asynchrone Benachrichtigungen über Bandbreitenänderungen abonnieren können, und das Übertragungsdienstobjekt ist langlebig. Wenn wir das machen:

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;

(Sie möchten eigentlich einen finally-Block verwenden, um sicherzustellen, dass der Event-Handler nicht durchgesickert ist.) Wenn wir uns nicht abmelden, wird der BandwidthUI mindestens so lange gültig sein wie der Übertragungsdienst.

Persönlich finde ich das selten - normalerweise, wenn ich ein Ereignis abonniere, lebt das Ziel dieses Ereignisses mindestens so lange wie der Herausgeber - ein Formular hält so lange wie der Button, der sich zum Beispiel darauf befindet. Es lohnt sich, über dieses potenzielle Problem Bescheid zu wissen, aber ich denke, einige Leute machen sich darüber Sorgen, wenn sie es nicht brauchen, weil sie nicht wissen, in welche Richtung die Referenzen gehen.

EDIT: Dies ist die Antwort auf Jonathan Dickinsons Kommentar. Schauen Sie sich zunächst die Dokumente für Delegate.Equals (object) an, die das Gleichheitsverhalten eindeutig angeben.

Zweitens ist hier ein kurzes, aber vollständiges Programm, um zu zeigen, dass die Abmeldung funktioniert:

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();
    }
}

Ergebnisse:

Raising Foo
Subscriber.FooHandler()
Raising Foo
No handlers

(Getestet unter Mono und .NET 3.5SP1.)

Weiter bearbeiten:

Dies soll beweisen, dass ein Event-Publisher abgeholt werden kann, solange noch Verweise auf einen Abonnenten vorhanden sind.

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);
    }
}

Ergebnisse (in .NET 3.5SP1 verhält sich Mono hier anscheinend etwas merkwürdig. Wird dies einige Zeit untersuchen):

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
176
Jon Skeet

In deinem Fall geht es dir gut. Ich habe Ihre Frage ursprünglich rückwärts gelesen, dass ein Abonnent den Gültigkeitsbereich verlässt, nicht der Herausgeber. Wenn der Herausgeber des Ereignisses aus dem Geltungsbereich gerät, gilt das Verweise für den Abonnenten (natürlich nicht für den Abonnenten selbst!), Und es ist nicht erforderlich, sie explizit zu entfernen.

Meine ursprüngliche Antwort lautet wie folgt: Was passiert, wenn Sie ein Ereignis erstellen Abonnent und es den Gültigkeitsbereich verlassen, ohne sich abzumelden? Es gilt nicht für Ihre Frage, aber ich werde es für die Geschichte an Ort und Stelle belassen.

Wenn die Klasse immer noch über Event-Handler registriert ist, ist sie immer noch erreichbar. Es ist immer noch ein lebendes Objekt. Ein GC, der einem Ereignisdiagramm folgt, erkennt, dass es verbunden ist. Ja, Sie möchten die Ereignishandler explizit entfernen.

Nur weil das Objekt außerhalb seines ursprünglichen Zuordnungsbereichs liegt, bedeutet dies nicht, dass es ein Kandidat für die GC ist. Solange eine Live-Referenz vorhanden ist, ist sie live.

7
Eddie