265

I recently discovered that I can use lambdas to create simple event handlers. I could for example subscribe to a click event like this:

button.Click += (s, e) => MessageBox.Show("Woho");

But how would you unsubscribe it?

Zoe
  • 27,060
  • 21
  • 118
  • 148
Svish
  • 152,914
  • 173
  • 462
  • 620

1 Answers1

386

The C# specification explicitly states (IIRC) that if you have two anonymous functions (anonymous methods or lambda expressions) it may or may not create equal delegates from that code. (Two delegates are equal if they have equal targets and refer to the same methods.)

To be sure, you'd need to remember the delegate instance you used:

EventHandler handler = (s, e) => MessageBox.Show("Woho");

button.Click += handler;
...
button.Click -= handler;

(I can't find the relevant bit of the spec, but I'd be quite surprised to see the C# compiler aggressively try to create equal delegates. It would certainly be unwise to rely on it.)

If you don't want to do that, you'll need to extract a method:

public void ShowWoho(object sender, EventArgs e)
{
     MessageBox.Show("Woho");
}

...

button.Click += ShowWoho;
...
button.Click -= ShowWoho;

If you want to create an event handler which removes itself using a lambda expression, it's slightly trickier - you need to refer to the delegate within the lambda expression itself, and you can't do that with a simple "declare a local variable and assign to it using a lambda expression" because then the variable isn't definitely assigned. You typically get around this by assigning a null value to the variable first:

EventHandler handler = null;
handler = (sender, args) =>
{
    button.Click -= handler; // Unsubscribe
    // Add your one-time-only code here
}
button.Click += handler;

Unfortunately it's not even easy to encapsulate this into a method, because events aren't cleanly represented. The closest you could come would be something like:

button.Click += Delegates.AutoUnsubscribe<EventHandler>((sender, args) =>
{
    // One-time code here
}, handler => button.Click -= handler);

Even that would be tricky to implement within Delegates.AutoUnsubscribe because you'd have to create a new EventHandler (which would be just a generic type argument). Doable, but messy.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 2
    Exactly, if you look inside your compiled assembly using Reflector, you'll notice that the compiler has created a pointer for you anyway when you use lambda, it's just that you don't see it in Visual Studio – Raffaeu Sep 19 '13 at 09:04
  • 2
    @Raffaeu: Calling it a pointer is a bit misleading - it's not a pointer in the normal C# sense of the word. – Jon Skeet Sep 19 '13 at 10:03
  • 4
    Yes apologize, "the compilers will create the handler variable anyway" is it better? :) – Raffaeu Sep 19 '13 at 11:32
  • 2
    @Raffaeu: Possibly :) – Jon Skeet Sep 19 '13 at 11:33
  • @JonSkeet What about a scenario where the handler itself should occur only once , For instance a Loaded event on some UI component where you wan't to make some initialization and then unsubscribe from the handler. Ok i see that can be done if the EventHandler isn't in the local scope where it was declared. EventHandler loaded = (sender,args) => { (sender as Componenet).Loaded -= loaded ; // Init operations } ; _component.Loaded += loaded; – eran otzap Jun 10 '14 at 09:22
  • @eran: Yes, that's a valid scenario and should work. – Jon Skeet Jun 10 '14 at 09:37
  • Just detach with the matching lambda method signature, no declarations needed. `Object.Event -= (sender, args) => { };` – dynamiclynk Aug 15 '14 at 20:46
  • @DynamicLynk: No, that's not guaranteed to work (and won't with the compiler implementations I've used before). See the first couple of lines of my answer. – Jon Skeet Aug 15 '14 at 20:51
  • @JonSkeet I am using this method currently and the handler correctly detaches at least this is the case with .Net 4.0. I have verified the event fires only once each time the event is raised. – dynamiclynk Aug 15 '14 at 20:55
  • 2
    @DynamicLynk: I would be very interested in seeing the code - I strongly *suspect* it's not actually behaving as you think. You could ask a new question ("Why is this working when Jon Skeet claims it shouldn't...") or just mail me privately. – Jon Skeet Aug 15 '14 at 20:56
  • 2
    @JonSkeet Yep, indeed it wasn't detaching it was just simulating it because I was creating a new background worker object on each button click in my example. So the lambda detach doesn't work as you pointed out. Thanks for setting me straight :) – dynamiclynk Aug 15 '14 at 21:49
  • @LeeWhitney: If you want to add something substantial like that, it's better to do it as your own answer, or add a comment suggesting it. – Jon Skeet Apr 03 '15 at 17:35
  • @JonSkeet - I don't see why given that it was already discussed in the comments by yourself and others. Moreover, I don't view it as a different answer, rather as making your answer more complete by covering a common edge case. Finally, erans code was hard to find and read buried in his,comment. – whitneyland Apr 03 '15 at 21:25
  • @LeeWhitney: I'll have another look in the morning, and maybe write the same sort of material, but in my voice, so to speak. – Jon Skeet Apr 03 '15 at 21:30
  • @LeeWhitney: Okay, I've added a bit more now. – Jon Skeet Apr 04 '15 at 09:44