Dans votre cas, tout va bien. C'est l'objet qui publie les événements qui maintient en direct les cibles des gestionnaires d'événements. Donc si j'ai:
publisher.SomeEvent += target.DoSomething;
a alors publisher
une référence àtarget
mais pas l'inverse.
Dans votre cas, l'éditeur sera éligible pour le garbage collection (en supposant qu'il n'y ait pas d'autres références à celui-ci), donc le fait qu'il ait une référence aux cibles du gestionnaire d'événements n'est pas pertinent.
Le cas délicat est celui où l'éditeur a une longue durée de vie mais que les abonnés ne veulent pas l'être - dans ce cas, vous devez désabonner les gestionnaires. Par exemple, supposons que vous disposiez d'un service de transfert de données qui vous permette de vous abonner à des notifications asynchrones sur les modifications de bande passante et que l'objet de service de transfert a une longue durée de vie. Si nous faisons ceci:
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ésinscrivons pas, alors le service 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 se trouve dessus, par exemple. Cela 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 quel sens vont les références.
EDIT: C'est pour répondre au commentaire de Jonathan Dickinson. Tout d'abord, regardez la documentation pour Delegate.Equals (object) qui donne clairement le comportement d'égalité.
Deuxièmement, voici un programme court mais complet pour montrer le fonctionnement de la désinscription:
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.)
Modifier davantage:
Il s'agit de prouver qu'un éditeur d'événement peut être collecté tant 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. Nous examinerons cela quelque 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