I remember, back in late 2005 when .NET 2.0 was released and introduced anonymous delegates. I remember being decidedely unimpressed - they looked ugly and seemed to represent laziness and coding charlatonism.

Isn't it funny how over time you get used to the new stuff and it doesn't look alien anymore.

The 'hard' type

Recently I used these inside the Ukadc.Diagnostics project - and I think it demonstrates why I've started to like anonymous delegates so much. We have a class that has a number of constructor overloads:

And based on which constructor was used, the behaviour of the Dispose method has to vary. Originally I set out to just store a flag indicating the construction type and then do a switch based on that flag inside Dispose. However, I think this was much nicer:

Now the reality is I didn't get too much out of this that I couldn't have done without anonymous delegates. The reality is - you never will, after all anonymous delegates are just compiler trickery. However, they regularly help me spot an approach I may have overlooked in their absence.

I was recently working with an ADC customer and we had to come up with a way of wrapping an existing component and making an asynchronous call synchronous. The existing API looked something like this:

Where WorkItemCompletedEventArgs carries the id of the work item and which is unique and only ever processed once.

You've probably seen something similar in the past. You add a work item and an event fires when it's finished processing. However, work items have a huge deviation in the amount of time they take to process and they're not necessarily processed in the order that they're added to the queue. So, to make a ProcessWorkItem(WorkItem workItem, int millisecondsTimeout) method that blocks, you need to make sure you pair up the correct WorkItemCompleted event with *your* work item. Still with me?

'Hard' anonymous delegates made for a nice solution to this that looked as follows:

// block here until our WorkItem completes - but timeout if the call doesn't complete in timeif (!mre.WaitOne(millisecondsTimeout, true)) {thrownew TimeoutException(string.Format("The call did not complete within {0}ms and was timed out", millisecondsTimeout)); }}

Hopefully, you spotted the caution in the comments and haven't just pasted that code into your uber mission critical app - because it will leak memory with every call to ProcessWorkItem.

The reason for this is that by wiring up an event handler (...WorkItemCompleted += delegate...) we have created a reference between the ExistingApi type and the object that hosts that delegate.

Because you've already read Raymond's posts on anonymous delegates you'll be well aware that 'hard' anonumous delegates implement their particular flavour of magic by creating a new type. Indeed, if we take a look in WinDbg (!dumpheap -stat) at my leaky example after running it for a short time we'll see a worrying number of oddly named objects:

if (!callCompleted) {thrownew TimeoutException(string.Format("The call did not complete within {0}ms and was timedout", millisecondsTimeout)); }}

Even better there's no need to wait for .NET 3.5 to use any of this. All the examples presented here are written in good old .NET 2.0. Happy days (even if I am little bit late to the game with this one!)

Raymond's short series: "The implementation of anonymous methods in C# and its consequences":

Comments are closed for this post.

Posted by
Andrew Smith
@
11 Jul 2008
6:54 AM
People should really be careful when using anonymous delegates. Even in your "easy" example, most people probably wouldn't realize that you just defined 4 new nested types since each delegate references the TraceSource and holds different types of information (string, object, object[] or string & object[]).

Thanks for commenting - you've made me realise the error in my post. The 'easy' example wasn't an 'easy' example at all. It was a 'hard' one (because it requires nested types, easy ones the compiler just generates a new method on the defining class).

Thanks again - I've fixed the post up now to remove the error.

In most cases you shouldn't worry about the creation of these nested types - that's the beauty of abstraction, but you're right. Occasionally it's good to know what's actually going on so you're aware of the possible pitfalls.