In your case, everything is fine. It's the object which publishes the events which keeps the targets of the event handlers live. So if I have:
publisher.SomeEvent += target.DoSomething;
then publisher
has a reference to target
but not the other way round.
In your case, the publisher is going to be eligible for garbage collection (assuming there are no other references to it) so the fact that it's got a reference to the event handler targets is irrelevant.
The tricky case is when the publisher is long-lived but the subscribers don't want to be - in that case you need to unsubscribe the handlers. For example, suppose you have some data transfer service which lets you subscribe to asynchronous notifications about bandwidth changes, and the transfer service object is long-lived. If we do this:
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;
(You'd actually want to use a finally block to make sure you don't leak the event handler.) If we didn't unsubscribe, then the BandwidthUI
would live at least as long as the transfer service.
Personally I rarely come across this - usually if I subscribe to an event, the target of that event lives at least as long as the publisher - a form will last as long as the button which is on it, for example. It's worth knowing about this potential issue, but I think some people worry about it when they needn't, because they don't know which way round the references go.
EDIT: This is to answer Jonathan Dickinson's comment. Firstly, look at the docs for Delegate.Equals(object) which clearly give the equality behaviour.
Secondly, here's a short but complete program to show unsubscription working:
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();
}
}
Results:
Raising Foo
Subscriber.FooHandler()
Raising Foo
No handlers
(Tested on Mono and .NET 3.5SP1.)
Further edit:
This is to prove that an event publisher can be collected while there are still references to a subscriber.
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);
}
}
Results (in .NET 3.5SP1; Mono appears to behave slightly oddly here. Will look into that some time):
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
It depends. The System.Timers.Timer
has two modes of operation.
If SynchronizingObject
is set to an ISynchronizeInvoke
instance then the Elapsed
event will execute on the thread hosting the synchronizing object. Usually these ISynchronizeInvoke
instances are none other than plain old Control
and Form
instances that we are all familiar with. So in that case the Elapsed
event is invoked on the UI thread and it behaves similar to the System.Windows.Forms.Timer
. Otherwise, it really depends on the specific ISynchronizeInvoke
instance that was used.
If SynchronizingObject
is null then the Elapsed
event is invoked on a ThreadPool
thread and it behaves similar to the System.Threading.Timer
. In fact, it actually uses a System.Threading.Timer
behind the scenes and does the marshaling operation after it receives the timer callback if needed.
Best Answer
AFAIK, ClassBInstance is not garbage collected as long as there are events registered, because the event holds a reference to it.
You have to make sure that you unregister all events of instances that are no longer in use.
Important are cases where the registered instance is IDisposable, because the event could be fired when the instance is disposed. In this cases I found it easiest to let the instance register itself, and unregister in Dispose.