tags:

views:

190

answers:

2

Lets says we have 2 objects, Broadcaster and Listener. Broadcaster has an event called Broadcast to which Listener is subscribed. If Listener is disposed without unsubscribing from the Broadcast event it will be retained in memory because of the event delegate referencing it that Broadcaster contains.

What I'm curious about is if Broadcaster is disposed without Listener unsubscribing or Broadcaster setting Broadcast = null will Broadcaster be retained in memory?

I have not been able to locate anything with a hard answer to this question except one blogger who believes that not setting the event to null will keep the source in memory (found here).

I'd like to hear an explanation of why or why not.

Thanks.

UPDATE: Forum Thread where one developer indicates events should be set to null, but Jon Skeet indicates it's not necessary, but doesn't elaborate.

+3  A: 

Note that delegates don't keep the publisher alive (they only keep the target=subscriber alive), so no number of subscriptions will (by themselves) keep the broadcaster alive. As such, from this perspective it doesn't matter whether it is disposed or not. When there are no items referencing the broadcaster (and the event-subscriptions don't matter for this), it will be eligible for collection.

Essentially, a delegate is a (list of) pair(s) of MethodInfo and object references; the method to call, and the object to invoke as "arg0" (aka this). It simply doesn't have a reference to the object raising the event.

Here's evidence that a listener does not keep the source alive; you should see that "Source 1" gets collected, even though we still have the matching listener that is subscribed. As expected, "Listener 2" does not get collected, since we still have the matching broadcaster:

class DataSource
{
    public DataSource(string name) { this.name = name; }
    private readonly string name;
    ~DataSource() { Console.WriteLine("Collected: " + name); }

    public event EventHandler SomeEvent;
}
class DataListener
{
    public DataListener(string name) { this.name = name; }
    private readonly string name;
    ~DataListener() { Console.WriteLine("Collected: " + name); }
    public void Subscribe(DataSource source)
    {
        source.SomeEvent += SomeMethodOnThisObject;
    }
    private void SomeMethodOnThisObject(object sender, EventArgs args) { }
}

static class Program
{
    static void Main()
    {
        DataSource source1 = new DataSource("Source 1"),
                source2 = new DataSource("Source 2");
        DataListener listener1 = new DataListener("Listener 1"),
                listener2 = new DataListener("Listener 2");
        listener1.Subscribe(source1);
        listener2.Subscribe(source2);
        // now we'll release one source and one listener, and force a collect
        source1 = null;
        listener2 = null;
        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
        GC.WaitForPendingFinalizers(); // source 1 gets collected, ONLY

        Console.WriteLine("Done");
        Console.ReadLine();
        GC.KeepAlive(source2); // prevents collection due to optimisation
        GC.KeepAlive(listener1); // prevents collection due to optimisation
    }
}
Marc Gravell
Yes, but I could pose the response that the MethodInfo references the method to call which is on the subscriber therefore creating a reference between the broadcaster and subscriber. The explanation could be in the direction of the reference (Broadcaster -> Subscriber, not the reverse).
Dan Rigby
@Dan - Absulutely, it **is** in the direction broadcaster to subscriber, which is exactly why the reference doesn't keep the broadcaster alive.
Marc Gravell
+2  A: 

No. The delegate target in the Broadcast event references the Listener object. That will keep the Listener object alive. The Listener object doesn't have any reference back to the Broadcast object.

Watch out for terminology. Disposing the Broadcast object does nothing. It has to be garbage collected which can only happen when there's no references left to the object. When that happens, the delegate object will automatically be collected as well since the only reference to it is the internal list of delegate targets maintained by private event delegate object. That also removes the reference the delegate has to the listener. If there are no other references to the listener, it will be collected as well. If it still is, it will just no longer get event notifications. Long story short: you don't have to explicitly set the event to null in the Broadcast class.

Not quite the same in the listener, it is referenced by the event it subscribed to. If it is declared unfit for business (disposed) but the broadcaster is still live then it should remove its event subscription explicitly. The SystemEvents class is an extreme version of that, its events are static. Firing events on a delegate that references a disposed listener is something you tend to notice.

Most practical object models try to ensure listener objects disappear when the parent goes. Windows Forms would be a good example. No need to explicitly unsubscribe events then.

Hans Passant
Actually, you can set an event to null from within the class that declares it.
Alfred Myers
What do you mean by "Which is something the Broadcast class cannot do, it can't generate a delegate instance to unsubscribe."?
Alfred Myers
You're right, I tweaked the post.
Hans Passant