Skip Navigation LinksHome > View Post

Anonymous Delegates, how I love thee

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.

Funnily enough, it wasn't until the release of .NET 3.5 and my time spent experimenting with lambdas that I really gave anonymous delegates a chance.

And oh, how my views have changed!

The 'easy' type

There isn't too much special about the 'easy' kind of anonymous delegates, they're really just shorthand. e.g.

ThreadPool.QueueUserWorkItem(delegate(object ignored)
{
    // do something on the ThreadPool
    Console.WriteLine("Hello from a ThreadPool thread!");
});

Whilst this 'easy' type can be very handy - there's even more value to be had from the 'hard' type.

The 'hard' type

I've stolen the terms 'easy' and 'hard' types from Raymond Chen's excellent series on anonymous delegates. The hard type is where the delegate directly references a member from the method body.

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:

internal MethodProfiler(TraceSource source, string message);
internal MethodProfiler(TraceSource source, string format, params object[] args);
internal MethodProfiler(TraceSource source, object data);
internal MethodProfiler(TraceSource source, params object[] data);

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:

private delegate void Disposal();

private readonly Disposal _disposal;
private bool _disposed;

internal MethodProfiler(TraceSource source, string message)
{
    source.TraceEvent(TraceEventType.Start, 0, message);
    _disposal = delegate() { source.TraceEvent(TraceEventType.Stop, 0, message); };
}

internal MethodProfiler(TraceSource source, string format, params object[] args)
{
    source.TraceEvent(TraceEventType.Start, 0, format, args);
    _disposal = delegate() { source.TraceEvent(TraceEventType.Stop, 0, format, args); };
}

internal MethodProfiler(TraceSource source, object data)
{
    source.TraceData(TraceEventType.Start, 0, data);
    _disposal = delegate() { source.TraceData(TraceEventType.Stop, 0, data); };
}

internal MethodProfiler(TraceSource source, params object[] data)
{
    source.TraceData(TraceEventType.Start, 0, data);
    _disposal = delegate() { source.TraceData(TraceEventType.Stop, 0, data); };
}

void IDisposable.Dispose()
{
    if (!_disposed)
    {
        _disposed = true;
        _disposal();
    }
}

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:

void AddWorkItem(WorkItem workItem);
event EventHandler<WorkItemCompletedEventArgs> WorkItemCompleted;

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:

// CAUTION - Do not use me! I will leak memory! Read on...
public void ProcessWorkItem(WorkItem workItem, int millisecondsTimeout)
{
    ManualResetEvent mre = new ManualResetEvent(false);

    // register our delegate which checks to see we have the correct work item
    // before signalling the blocked thread to continue
    ExistingApi.WorkItemCompleted += delegate(object sender, WorkItemCompletedEventArgs args)
    {
        if (args.WorkItemId == workItem.Id)
        {
            mre.Set();
        }
    };

    // add our workitem to the queue
    ExistingApi.AddWorkItem(workItem);

    // block here until our WorkItem completes - but timeout if the call doesn't complete in time
    if (!mre.WaitOne(millisecondsTimeout, true))
    {
        throw new 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:

      MT    Count    TotalSize Class Name
00f816bc      973        19460 ExampleWrapper+<>c__DisplayClass2

Removing the leak

Fixing the problem is easy enough. We only need to unhook the delegate when we're done with it:

public void ProcessWorkItem(WorkItem workItem, int millisecondsTimeout)
{
    ManualResetEvent mre = new ManualResetEvent(false);

    EventHandler<WorkItemCompletedEventArgs> del = delegate(object sender, WorkItemCompletedEventArgs localArgs)
    {
        if (localArgs.WorkItem == workItem)
        {
            mre.Set();
        }
    };

    ExampleApi.WorkItemCompleted += del;

    ExampleApi.AddWorkItem(workItem);

    bool callCompleted = mre.WaitOne(millisecondsTimeout, true);

    // unhook the delegate
    ExampleApi.WorkItemCompleted -= del;

    if (!callCompleted)
    {
        throw new 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":

Tags: C#

 
Josh Post By Josh Twist
11:41 AM
10 Jul 2008

» Next Post: To var or not to var
« Previous Post: A Silverlight 2 Databinding Cornucopia

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[]).

Posted by Josh @ 11 Jul 2008 8:07 AM
Hi Andrew,

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.

© 2005 - 2014 Josh Twist - All Rights Reserved.