web-dev-qa-db-fra.com

C # - fonctions anonymes et gestionnaires d'événements

J'ai le code suivant:

public List<IWFResourceInstance> FindStepsByType(IWFResource res)  
{  
    List<IWFResourceInstance> retval = new List<IWFResourceInstance>();  
    this.FoundStep += delegate(object sender, WalkerStepEventArgs e)   
                      {   
                        if (e.Step.ResourceType == res) retval.Add(e.Step);   
                      };  
    this.Start();  
    return retval;
}  

Remarquez comment j'enregistre mon membre d'événement (FoundStep) à la fonction anonyme locale sur place.

Ma question est: quand la fonction 'FindStepByType' prendra fin - la fonction anonyme sera-t-elle supprimée automatiquement de la liste des délégués de l'événement ou dois-je la supprimer manuellement avant de quitter la fonction? (et comment je fais ça?)

J'espère que ma question était claire.

22
Adi Barda

Votre code a quelques problèmes (certains que vous et d'autres avez identifiés):

  • Le délégué anonyme ne peut pas être supprimé de l'événement comme codé.
  • Le délégué anonyme vivra plus longtemps que la durée de vie de la méthode qui l'appelle car vous l'avez ajouté à FoundStep qui est membre de this.
  • Chaque entrée dans FindStepsByType ajoute un autre délégué anonyme à FoundStep.
  • Le délégué anonyme est une fermeture et prolonge efficacement la durée de vie de retval, donc même si vous arrêtez de référencer retval ailleurs dans votre code, il est toujours détenu par le délégué anonyme.

Pour résoudre ce problème, et toujours utiliser un délégué anonyme, affectez-le à une variable locale, puis supprimez le gestionnaire dans un bloc enfin (nécessaire au cas où le gestionnaire lèverait une exception):

  public List<IWFResourceInstance> FindStepsByType(IWFResource res)
  {
     List<IWFResourceInstance> retval = new List<IWFResourceInstance>();
     EventHandler<WalkerStepEventArgs> handler = (sender, e) =>
     {
        if (e.Step.ResourceType == res) retval.Add(e.Step);
     };

     this.FoundStep += handler;

     try
     {
        this.Start();
     }
     finally
     {
        this.FoundStep -= handler;
     }

     return retval;
  }

Avec C # 7.0+, vous pouvez remplacer le délégué anonyme par une fonction locale, obtenant le même effet:

    public List<IWFResourceInstance> FindStepsByType(IWFResource res)
    {
        var retval = new List<IWFResourceInstance>();

        void Handler(object sender, WalkerStepEventArgs e)
        {
            if (e.Step.ResourceType == res) retval.Add(e.Step);
        }

        FoundStep += Handler;

        try
        {
            this.Start();
        }
        finally
        {
            FoundStep -= Handler;
        }

        return retval;
    }
41
Kit

Vous trouverez ci-dessous une approche sur la façon de désinscrire un événement dans une méthode anonyme:

DispatcherTimer _timer = new DispatcherTimer();
_timer.Interval = TimeSpan.FromMilliseconds(1000);
EventHandler handler = null;

int i = 0;

_timer.Tick += handler = new EventHandler(delegate(object s, EventArgs ev)
{
    i++;
    if(i==10)
        _timer.Tick -= handler;
});

_timer.Start();
9
Amit

Non, il ne sera pas supprimé automatiquement. En ce sens, il n'y a pas de différence entre une méthode anonyme et une méthode "normale". Si vous le souhaitez, vous devez vous désabonner manuellement de l'événement.

En fait, il va capturer d'autres variables (par exemple res dans votre exemple) et les garder en vie (empêche le garbage collector de les collecter) aussi.

5
Mehrdad Afshari

Lorsque vous utilisez un délégué anonyme (ou une expression lambda) pour vous abonner à un événement, vous ne pouvez pas vous désabonner facilement de cet événement ultérieurement. Un gestionnaire d'événements n'est jamais automatiquement désabonné.

Si vous regardez votre code, même si vous déclarez et vous abonnez à l'événement dans une fonction, l'événement auquel vous vous abonnez est dans la classe, donc une fois abonné, il sera toujours abonné même après la fin de la fonction. L'autre chose importante à réaliser est que chaque fois que cette fonction est appelée, elle s'abonne à nouveau à l'événement. Ceci est parfaitement légal car les événements sont essentiellement des délégués de multidiffusion et autorisent plusieurs abonnés. (Cela peut ou non être votre intention.)

Pour vous désinscrire du délégué avant de quitter la fonction, vous devez stocker le délégué anonyme dans une variable de délégué et ajouter le délégué à l'événement. Vous devez ensuite pouvoir supprimer le délégué de l'événement avant la fermeture de la fonction.

Pour ces raisons, si vous devez vous désinscrire de l'événement à un moment ultérieur, il n'est pas recommandé d'utiliser des délégués anonymes. Voir Comment: vous abonner à des événements et vous y désabonner (Guide de programmation C #) (en particulier la section intitulée "Pour vous abonner à des événements en utilisant une méthode anonyme ").

2
Scott Dorman