Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Anonymous Methods as Event Handlers – Part 2

0.00/5 (No votes)
13 Sep 2009 1  
Anonymous methods as event handlers

The previous post discussed having anonymous methods as event handlers and ended with a question – why doesn’t unsubscription work while subscription works out alright?

Vivek got the answer spot on – the way the C# compiler handles and translates anonymous methods is the reason.

Here’s the code involved:

 1: public void Initialize()
 2: {
 3:     control.KeyPressed += IfEnabledThenDo(control_KeyPressed);
 4:     control.MouseMoved += IfEnabledThenDo(control_MouseMoved);
 5: }
 6:
 7: public void Destroy()
 8: {
 9:     control.KeyPressed -= IfEnabledThenDo(control_KeyPressed);
10:     control.MouseMoved -= IfEnabledThenDo(control_MouseMoved);
11: }
12:
13: public EventHandler<Control.ControlEventArgs>
           IfEnabledThenDo(EventHandler<Control.ControlEventArgs> actualAction)
14: {
15:     return (sender, args) => { if (args.Control.Enabled) actualAction(sender, args); };
16: }

The compiler translates IfEnabledThenDo into this:

1: public EventHandler<Control.ControlEventArgs>
          IfEnabledThenDo(EventHandler<Control.ControlEventArgs> actualAction)
2: {
3:     <>c__DisplayClass1 CS$<>8__locals2 = new <>c__DisplayClass1();
4:     CS$<>8__locals2.actualAction = actualAction;
5:     return new EventHandler<Control.ControlEventArgs>(
                      CS$<>8__locals2.<IfEnabledThenDo>b__0);
6: }

Now the problem should be fairly obvious – every time the function is called, a new object gets created, and the event handler returned actually refers to a method (<IfEnabledThenDo>b__0) on the new instance. And that’s what breaks unsubscription. –= will not remove a delegate of a different instance of the same class from the invocation list – if it did, the consequences would not be pleasant if multiple instances of the same class subscribe to an event.

But why does the compiler translate our lambda expression this way? Raymond Chen has a great blog post explaining why, but the short answer is that it is needed to “hold” the actualAction (the method parameter to IfEnabledThenDo) so that it is available when the event handler actually executes.

Now that we know why, the way to get around this issue is to cache the delegate instance returned by IfEnabledThenDo and use the same instance for subscription and unsubscription.

 1: EventHandler<Control.ControlEventArgs> keyPressed;
 2: EventHandler<Control.ControlEventArgs> mouseMoved;
 3:
 4: public void Initialize()
 5: {
 6:    keyPressed = IfEnabledThenDo(control_KeyPressed);
 7:    mouseMoved = IfEnabledThenDo(control_MouseMoved);
 8:
 9:    control.KeyPressed += keyPressed;
10:    control.MouseMoved += mouseMoved;
11: }
12:
13: public void Destroy()
14: {
15:    control.KeyPressed -= keyPressed;
16:    control.MouseMoved -= mouseMoved;
17: }

Knowing how things work under the hood has its advantages, I guess. :)

P.S.: A very small syntactic change to the original example would have made the code work right away. If you’ve followed along this far, you should be able to figure out why.

 1: public void Initialize()
 2: {
 3:    actualAction = control_KeyPressed;
 4:    control.KeyPressed += IfEnabledThenDo();
 5: }
 6:
 7: public void Destroy()
 8: {
 9:     control.KeyPressed -= IfEnabledThenDo();
10: }
11:
12: EventHandler<Control.ControlEventArgs> actualAction;
13: public EventHandler<Control.ControlEventArgs> IfEnabledThenDo()
14: {
15:     return (sender, args) => { if (args.Control.Enabled) actualAction(sender, args); };
16: }

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here