Justin a.k.a. The Code Monkey

Code Monkey [kohd muhng'-kee] n : 1. Creature known for it's ability to transform caffeine into code. 2. Justin Jones
posts - 10 , comments - 27 , trackbacks - 0

Wednesday, September 10, 2008

Delegates 101

It doesn't seem like all that long ago I was trying to get my head around exactly what a delegate was.  Honestly it wasn't that hard for me coming from a C++ background, since they were an awful lot like function pointers.  But still, the concept was just a little different. 

In the interest of making your UI more responsive, it's a good idea to thread some of the more intense work that occurs in your application.  As soon as you say "Threading", a lot of people blanch and go pale, but it's really not that difficult, in fact, one class makes it downright easy.

The BackgroundWorker component (System.ComponentModel.BackgroundWorker) is specifically designed to take some intensive work and make threading it easy.  It's a component, so you can drop it right on your form and just simply subscribe to the events DoWork and RunWorkerCompleted.  DoWork is the event that fires on the worker thread and is where your intensive work that would otherwise freeze the UI would occur, and RunWorkerCompleted is the event that fires back on the main thread (or more accurately the thread that initiated the work) and allows you to get the results of the threaded work.  RunWorkerAsync() is what tells the worker to spawn of the worker thread and do the work.

I like using this component, because it takes a lot of the complexity of working directly with with the Thread class away and handles it for you.  Less plumbing to maintain.

I recently ran into a case where I wanted a component to behave similarly, where you would simply call a method to say "go do your thing" and then fire an event when it was done letting the caller know it could get the data now.  The work would occur on a worker thread so that it would not freeze the UI, but the UI would not need to know anything about that.  As far as the UI was concerned, it called a method and later on got an event, without ever having called any threading code.

Here's how that played out.  I've created some demo code structured in the same manner, but it's been genericized.  First off I needed something for this code to do, so it will return an IEnumerable instance of DataClass.  DataClass is just a data class that I can bind to a grid on a form. 

I drop a DataGrid, a binding source pointed at DataClass, and a few buttons and I've got my framework done.  I created DataService to handle to complexities of retrieving the data, so that in the button click event it simply calls dataService.GetDataAsync() then the DataRetrieved event fires so that the form can retrieve the data from the event args and plug it into the binding source, updating the grid.

Internally to the DataService, is where the cool stuff happens.  Here's GetDataAsync()

public void GetDataAsync()
{
    using (BackgroundWorker worker = new BackgroundWorker())
    {
        worker.DoWork += new DoWorkEventHandler(worker_DoWork);
        worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
        worker.RunWorkerAsync();
    }
}

I could have dropped BackgroundWorker onto the design surface of DataService, since I made it a component, but I decided not to so that I can show some delegate goodness.  What you see here is DataService subscribing to DoWork and RunWorkerCompleted then kicking off the thread.  What we've done here is pass a delegate into the event handler.  DoWorkEventHandler and RunWorkerCompletedEventHandler are both predefined delegates that match a specific interface, allowing it to be passed to the event handler.  The parameter to the delegates simply take a pointer to the method.  If the method does not match the signature, a compile error will be thrown. 

This is how we had to do it in .NET 1.0, 1.1 days, and if you subscribe to events in the designer, this method is still used, and the code is generated and placed in InitializeComponent.  However, we can make it cooler.

.NET 2.0 gave us anonymous delegates, which were essentially a way of declaring an anonymous method.  The reason I like this approach is because it keeps all the code related to a given task in the same place rather than spread out among several methods.  This isn't always practical or the best design, but in a lot of cases it is.  That code would look like this:

public void GetDataAsync()
{
    using (BackgroundWorker worker = new BackgroundWorker())
    {
        worker.DoWork += new DoWorkEventHandler(delegate(object sender, DoWorkEventArgs e) { e.Result = GetSomeData(); });
        worker.RunWorkerCompleted +=
            new RunWorkerCompletedEventHandler(delegate(object sender, RunWorkerCompletedEventArgs e)
                                                   {
                                                       IEnumerable<DataClass> data = (IEnumerable<DataClass>) e.Result;
                                                       OnDataRetrieved(data);
                                                   });
        worker.RunWorkerAsync();
    }
}

I've taken the contents of worker_DoWork and worker_RunWorkerCompleted and moved them inline to an anonymous delegate.  The signature still needs to match the delegate, and Intellisense won't help you out much here.  What's important to realize is that the code will operate exactly the same way as the other code, so the code in the delegate wont actually execute until the event fires.  There are several resources that explain what the compiler actually does to make this work, so I won't cover it now. 

There's still some redundancy here.  The compiler is smart enough to know what the signature of the delegate is, so we don't necessarily have to define it.  We can actually write the same method like this:

public void GetDataAsync()
{
    IEnumerable<DataClass> data = null;
    using (BackgroundWorker worker = new BackgroundWorker())
    {
        worker.DoWork += delegate { data = GetSomeData(); };
        worker.RunWorkerCompleted += delegate { OnDataRetrieved(data); };
        worker.RunWorkerAsync();
    }
}

Oops, I snuck in another change as well.  You'll notice that data is defined as a variable in the method.  No worries! The compile handles all this for you too.  Again, the classes and stuff that are generated here are discussed in plenty of other places, so I'll leave the details alone for now, but it does work.  What this allows us to do is to not box and unbox the data using the Result property of the event args which is of type object.  Since we no longer need event args, we don't even have to specify them.  The compiler knows that DoWork takes a DoWorkEventHandler, and that the anonymous delegate can fit that bill, so it allows it.  The code is just about as small as it was as it was initially, but we've refactored out two event handler methods.  That's pretty cool.

But I'm not done yet.  We can take it one step further.  In 3.5 we got Linq, and Linq gave us some pretty cool stuff, and a lot of it can be used outside of the context of Linq.  Specifically I'm talking about Lambda expressions. 

A Lambda expression is basically a syntax for specifying an anonymous delegate.  Again, the compiler is smart enough to know the signature of the delegate, so we don't always have to type that code out.  You've probably seen code like

data.Where(x => x.Beta < 100);

Since data is IEnumerable<DataClass> and Where takes a Predicate, it knows that the single parameter of the predicate should be a DataClass and the return value is a bool.  Therefore this code equates to

data.Where(delegate(DataClass x)
               {
                   return x.Beta < 100;
               });

We can also use Lambda expressions for all kinds of anonymous delegates.  Now we can write the GetDataAsync code like this:

public void GetDataAsync()
{
    IEnumerable<DataClass> data = null;
    using (BackgroundWorker worker = new BackgroundWorker())
    {
        worker.DoWork += ((sender, e) => data = GetSomeData());
        worker.RunWorkerCompleted += ((sender, e) => OnDataRetrieved(data));
        worker.RunWorkerAsync();
    }
}
How cool is that? 

Posted On Wednesday, September 10, 2008 2:01 PM | Comments (2) | Filed Under [ .Net 3.5 ]

Powered by: