How to remove a lambda event handler [duplicate]
Asked Answered
T

1

267

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?

Thermomotor answered 1/9, 2009 at 12:24 Comment(8)
See here: stackoverflow.com/questions/183367Premises
Have you tried the -= operator?Recurved
@Svish: A lambda is essentially an anonymous method.Tartaric
Aha, so that would be a yes then.Thermomotor
Unless I'm missing a subtle difference, your question is answered here: #806329, though its accepted answer is wrong (but corrected in a comment).Mcclees
Yeah, I have found at least 3 similar questions now, but this one just got a very good answer.Thermomotor
Also related: #1747735Spermatozoid
No, essentially it is a 'no'.Fetiparous
S
387

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.

Stump answered 1/9, 2009 at 12:35 Comment(15)
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 StudioMercerize
@Raffaeu: Calling it a pointer is a bit misleading - it's not a pointer in the normal C# sense of the word.Stump
Yes apologize, "the compilers will create the handler variable anyway" is it better? :)Mercerize
@Raffaeu: Possibly :)Stump
@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;Heavyhearted
@eran: Yes, that's a valid scenario and should work.Stump
Just detach with the matching lambda method signature, no declarations needed. Object.Event -= (sender, args) => { };Snake
@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.Stump
@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.Snake
@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.Stump
@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 :)Snake
@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.Stump
@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.Sestos
@LeeWhitney: I'll have another look in the morning, and maybe write the same sort of material, but in my voice, so to speak.Stump
@LeeWhitney: Okay, I've added a bit more now.Stump

© 2022 - 2024 — McMap. All rights reserved.