views:

595

answers:

3

I'm not sure if I'm entirely clear on the implications of attaching to events in objects.

This is my current understanding, correct or elaborate:

1. Attaching to local class events do not need to be detached

Examples:

this.Closing += new System.ComponentModel.CancelEventHandler(MainWindow_Closing);

public event EventHandler OnMyCustomEvent = delegate { };

I'm assuming that when your object is disposed or garbage collected, the functions are deallocated and would automatically detach from the events.

2. Attaching to objects you no longer need (= null;) have to be detached from

Examples: Attaching to a timer's Elapsed event, which you only respond to once. I would assume you need to store the Timer in a local variable so you can detached the Elapsed event after the event fires. Thus, declaring the timer in a local method scope like so would result in a leak:

System.Timers.Timer myDataTimer = new System.Timers.Timer(1000); myDataTimer.Elapsed += new System.Timers.ElapsedEventHandler(myDataTimer_Elapsed);

3. Attaching to events in a local object to your class does not require disposing?

For example, if you had an ObservableCollection that your creates, monitors, and lets die. If you attached to the CollectionChanged event using a local, private function, wouldn't this function deallocate when your class is garbage collected, causing the ObservableCollection to also be freed?

I'm sure I have places where I've stopped using objects and have failed to detach from an event (for example, the timer example I made), so I'm looking for a clearer explanation on how this works.

+11  A: 

I think you're making it more complicated than it needs to be. You just need to remember two things:

  • When you subscribe to an event, the event's "owner" (the publisher) generally keeps a reference to the delegate you subscribe with.
  • If you use an instance method as the action of a delegate, then the delegate has a reference to its "target" object.

This means that if you write:

publisher.SomeEvent += subscriber.SomeMethod;

Then subscriber won't be eligible for garbage collection before publisher is unless you unsubscribe later.

Note that in many cases, subscriber is just this:

publisher.SomeEvent += myDataTimer_Elapsed;

is equivalent to:

publisher.SomeEvent += this.myDataTimer_Elapsed;

assuming it's an instance method.

There is no reverse relationship just due to event subscription - in other words the subscriber doesn't keep the publisher alive.

See my article on events and delegates for more information, by the way.

Jon Skeet
You're exactly right, I am over complicating things. When looking for examples, almost all of them showed detaching from the event, which only led me to believe that the subscriber could keep the publisher alive.
Will Eddins
Stupid Question: Does that imply that if the publisher subscribes to an event on the subscriber, then neither of the two can/will be collected?
SnOrfus
No, it means there's a circular reference - as soon as there are no *other* rooted references to either of them, they'll both be eligible for GC.
Jon Skeet
A: 

The relevant case where you have to unsubscribe from an event is like this:

public class A
{
    // ...
    public event EventHandler SomethingHappened;
}

public class B
{
    private void DoSomething() { /* ... */ } // instance method

    private void Attach(A obj)
    {
       obj.SomethingHappened += DoSomething();
    }
}

In this scenario, when you dispose of a B, there will still be a dangling reference to it from obj's event handler. If you want to reclaim the B's memory, then you need to detach B.DoSomething() from the relevant event handler first.

You could run into the same thing if the event subscription line looked like this, of course:

obj.SomethingHappened += someOtherObject.Whatever.DoSomething();

Now it's someOtherObject that's on the hook and can't be garbage collected.

mquander
+2  A: 

The remaining references preventing garbage collection has one more effect that may be obvious but nontheless not yet stated in this thread; the attached event handler will be excuted as well.

I have experienced this a couple of times. One was when we had an application that gradually became slower and slower the longer it run. The application created the user interface in a dynamic fashion by loading user controls. The container made the user controls subscribe to certain events in the environment, and one of these were not unsubscribed from when the controls were "unloaded".

After a while this led to a large number of event listeners being executed each time that particular event was raised. This can of course lead to serious race conditions when a good number of "sleeping" instances suddenly wake up and try to act on the same input.

In short; if you write code to hook up an event listener; make sure that you release as soon as it's not needed any longer. I almost dare to promise it will save you from at least one headache at some point in the future.

Fredrik Mörk