web-dev-qa-db-fra.com

Est-il nécessaire de supprimer explicitement les gestionnaires d'événements en C #

J'ai une classe qui propose quelques événements. Cette classe est déclarée globalement mais pas instanciée sur cette déclaration globale - elle est instanciée au besoin dans les méthodes qui en ont besoin.

Chaque fois que cette classe est nécessaire dans une méthode, elle est instanciée et les gestionnaires d'événements sont enregistrés. Est-il nécessaire de supprimer explicitement les gestionnaires d'événements avant que la méthode ne soit hors de portée?

Lorsque la méthode sort du domaine, il en va de même de l'instance de la classe. Le fait de laisser des gestionnaires d'événements enregistrés avec cette instance qui sort du domaine d'application a-t-il une implication d'empreinte mémoire? (Je me demande si le gestionnaire d'événements empêche le GC de voir l'instance de classe comme n'étant plus référencée.)

113
rp.

Dans votre cas, tout va bien. C'est l'objet qui publie les événements qui maintient les cibles des gestionnaires d'événements en direct. Donc si j'ai:

publisher.SomeEvent += target.DoSomething;

alors publisher a une référence à target mais pas l'inverse.

Dans votre cas, l'éditeur va être éligible pour le ramasse-miettes (en supposant qu'il n'y ait pas d'autres références), donc le fait qu'il ait une référence aux cibles du gestionnaire d'événements n'a pas d'importance.

Le cas délicat est lorsque l'éditeur a une longue durée de vie mais que les abonnés ne veulent pas être - dans ça cas où vous devez vous désinscrire des gestionnaires. Par exemple, supposons que vous disposiez d'un service de transfert de données qui vous permet de vous abonner à des notifications asynchrones sur les changements de bande passante et que l'objet du service de transfert soit de longue durée. Si nous faisons cela:

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;

(Vous voudriez en fait utiliser un bloc finally pour vous assurer de ne pas divulguer le gestionnaire d'événements.) Si nous ne nous désinscrivions pas, le BandwidthUI vivrait au moins aussi longtemps que le service de transfert.

Personnellement, je rencontre rarement cela - généralement si je m'abonne à un événement, la cible de cet événement vit au moins aussi longtemps que l'éditeur - un formulaire durera aussi longtemps que le bouton qui s'y trouve, par exemple. Il vaut la peine de connaître ce problème potentiel, mais je pense que certaines personnes s'en inquiètent quand elles n'en ont pas besoin, car elles ne savent pas dans quelle direction aller les références.

EDIT: Ceci est pour répondre au commentaire de Jonathan Dickinson. Tout d'abord, regardez les documents pour Delegate.Equals (object) qui donnent clairement le comportement d'égalité.

Deuxièmement, voici un programme court mais complet pour montrer que le désabonnement fonctionne:

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

Résultats:

Raising Foo
Subscriber.FooHandler()
Raising Foo
No handlers

(Testé sur Mono et .NET 3.5SP1.)

Édition supplémentaire:

Il s'agit de prouver qu'un éditeur d'événements peut être collecté alors qu'il existe encore des références à un abonné.

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

Résultats (dans .NET 3.5SP1; Mono semble se comporter un peu bizarrement ici. Il examinera cela un certain temps):

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

Dans votre cas, vous allez bien. J'ai lu à l'origine votre question à l'envers, qu'un abonné sortait du cadre, pas le éditeur. Si l'éditeur d'événement sort du cadre, les références vers l'abonné (pas l'abonné lui-même, bien sûr!) L'accompagnent et il n'est pas nécessaire de les supprimer explicitement.

Ma réponse originale est ci-dessous, à propos de ce qui se passe si vous créez un événement abonné et le laissez hors de portée sans vous désinscrire. Cela ne s'applique pas à votre question, mais je vais la laisser en place pour l'histoire.

Si la classe est toujours enregistrée via des gestionnaires d'événements, elle est toujours accessible. C'est toujours un objet vivant. Un GC suivant un graphique d'événement le trouvera connecté. Oui, vous souhaiterez supprimer explicitement les gestionnaires d'événements.

Ce n'est pas parce que l'objet est hors de sa portée d'origine qu'il est candidat à GC. Tant qu'une référence en direct demeure, elle est en direct.

7
Eddie