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.
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.