About the author

Steven Harmansteven harman :: makes sweet software with computers!

For recent posts and more about me, scroll to the bottom.

Subscribe

  • Subscribe to my feed. via RSS
  • Subscribe via email via email

News

Badges

  • Subtext Project
  • Support Subtext

Anonymous Delegates, Events, and Lambda Fun!

Events and delegates aren’t exactly a new concept in the .net world. I might even go so far as to say that they are fairly well understood by most experienced .net developers.

That’s not to say the concepts behind them are easy to grok... I’m just saying that if you know developer who’s been around the .net-block a few times, he/she probably has a pretty good grasp of what a delegate is and how they’re used.

Am I such a developer?

Up until yesterday I thought I was.

Here’s the scenario I found myself in: I had an event and I was dynamically subscribing event handlers (anonymous delegates) to it depending on the action a user took. However, before subscribing a new delegate I needed to make sure that all others were unsubscribed.

Subscribing to the event looked something like

   1: if (waf > 2)
   2: {
   3:     abutton.Click += (s, e) => { DoSomethingCool(); };
   4: }
   5: else
   6: {
   7:     abutton.Click += (s, e) => { ThisIsLame(); };
   8: }

Look at the nice, concise syntax the lambdas give me... oh I loves me those lambdas!

What about unsubscribing?

My first instinct was to clear the event’s InvocationList by setting the event to null.

   1: abutton.Click = null;

But that doesn’t work and it actually generates a compile-time error... something about how the event can only appear on the left hand side of += or -=. To which I say, WTF?

Next tried looking for a Clear() or Empty() method, but there’s no such thing. Normally this wouldn’t be a big deal as I could manually remove the my delegate (event handler) from the event using the -= operator.

   1: abutton.Click -= myClickHandler;

However, because I’ve attached anonymous delegates to the event I have no way of specifying which item to remove from the event’s InvocationList. So I don’t know what to specify on the right hand side of the -= operator. Crap!

When all else fails, ask a ninja!

Or in this case, my followers on Twitter. :)

I posed the question and a short code snippet to my Twitter feed to see if anyone had any suggestions. Phil took a stab in the dark and suggested

   1: a.Button.Click -= (s, e) => { DoSomethingCool(); }

Unfortunately I’d already attempted that. It compiled OK but the code was far from correct. The anonymous delegate on the right hand side of the -= operator resulted in a different delegate than the one on the right hand side of the += operator above. That is to say, the runtime didn’t know what object to remove from the invocation list, so it didn’t remove any.

The result was that I just kept adding new subscribers to the event, without ever removing any.

How about an Extension Method?

In a last ditch attempt I thought I’d try using some reflection to get the job done... and then wrap that code in an extension method.

   1: public static void Clear(this EventHandler eh)
   2: {
   3:     eh.GetInvocationList().ToList().Clear();
   4: }
   5:  
   6: public static void test()
   7: {
   8:     Button a = new Button();
   9:  
  10:     a.Click.Clear();
  11:  
  12:     a.Click += (s, e) => 
  13:         { Console.WriteLine("Phil Haack is my ninja!"); };
  14:  
  15:     a.Click.Clear();
  16: }

No dice! The compiler again complains about the event needing to be on the left hand side of the += or += operator.

Report it!

At that point I decided I should probably report this issue to Microsoft and see if it’s something that can be changed for the next release. However it turns out that the issue has already been reported, and subsequently ignored. Unfortunately, this seems to be a pretty standard practice from what I’ve seen at the Connect site in my days.

Despite the item being closed, I went ahead and added a validation comment, complete with code necessary to reproduce the problem. Who knows, maybe writing this post will draw some more attention to the issue and it will get some actual attention.

Technorati Tags: , , , ,

What others are saying.

# re: Anonymous Delegates, Events, and Lambda Fun!
Gravatar Aaron Fischer
Dec 20, 2007
I think you are almost there.
public static void Clear(this EventHandler eh)

{

foreach Delegate de in eh.GetInvocationList()
{
eh.RemoveImpl( de);
}

}
# re: Anonymous Delegates, Events, and Lambda Fun!
Gravatar Haacked
Dec 20, 2007
Take a look at Reflector and you'll understand what's going on.

Everytime you declare:
(s, e) => { DoSomethingCool(); }

The compiler generates a new delegate. This might seem like bad behavior, but you're dealing with a trivial case here. What if the delegate had a non-trivial amount of code.

The compiler is supposed to check the entire par tree every time, do a diff and determine it is indeed the same delegate?

Might make sense in this specific scenario (adding, removing events), but I imagine they didn't want to add that magic just yet.

The best you can do is store the delegate somewhere and do this:

EventHandler handler = (s, e) => { DoSomething(); };
foo.Bar += handler;
foo.Bar

Or don't use anonymous delegates.

The compiler would have to "magically" know that your two anonymous delegates are exactly the same. How would that look if the implementations were non trivial?
# re: Anonymous Delegates, Events, and Lambda Fun!
Gravatar Haacked
Dec 20, 2007
@Aaron, the problem is that the event can only appear on the left hand side of += or -= *except* when used from within the declaring type.

So if you can modify the type *with* the event, then you're golden. Otherwise, you're stuck.
# re: Anonymous Delegates, Events, and Lambda Fun!
Gravatar Jon Kruger
Dec 20, 2007
Good luck getting this one fixed. :)

I'm guessing that they'll just tell you that since you can handle the event with a normal event handler method that it's not worth their time to fix it.

That syntax for handling events is pretty cool though... very concise.
# re: Anonymous Delegates, Events, and Lambda Fun!
Gravatar Steven Harman
Dec 20, 2007
@Phil, I totally get why trying to remove an anonymous delegate in the case of the -= operator doesn't work... and I'm fine with that. What I don't get is why there isn't some built-in way to clear the event's InvocationList.

So the problem is with the compile time error about the event only being allowed on the left hand side of the assignment operators.
# re: Anonymous Delegates, Events, and Lambda Fun!
Gravatar Jason
Dec 21, 2007
I just solved this problem for one of the developers on my team today using GetInvocationList and it works. Just need to get all the items from the invocation list, cast them to the delegate type and use the -= operator. Here is an example:

public void ClearEventHanlders()
{
lock (this)
{
foreach (var item in this.SomeEvent.GetInvocationList())
{
this.SomeEvent -= (EventHandler)item;
}
}
}
# re: Anonymous Delegates, Events, and Lambda Fun!
Gravatar Denys
Dec 26, 2007
Wow thanks! I’m a regular reader and enjoy your work. Keep it up!
# re: Anonymous Delegates, Events, and Lambda Fun!
Gravatar Andy
Jan 02, 2008
forgive me if I'm being dense, but why not have something like this:

class Test
{
public event EventHandler MyEvent;

public Test()
{
EventHandler e = delegate { Console.WriteLine("blah"); };
MyEvent += e; // Add anonymous delegate
MyEvent(null, null); // Invoke anonymous delegate
MyEvent -= e; // Remove anonymous delegate
}
}

# re: Anonymous Delegates, Events, and Lambda Fun!
Gravatar Ristorasto
Mar 06, 2008
If you just want to remove an anonymous callback method from an delegate (and know the index) use the following.

private delegate void Machines();
private int index = 1;

Machines ma = delegate(){};
ma += delegate(){};

//Remove anonymous callback method
ma -= (Machines)ma.GetInvocationList()[i];


Hope that works for everybody
Comments have been closed on this topic.