Thursday, April 14, 2005

Beginning with delegates in C#


Delegates are one of the mind benders of the .NET framework. Not once have I been teaching the concept without feeling a bit of inadequacy at drawing up a clear and concise explanation. Unless a developer has worked with function pointers in C, they are a new concept. I'll start off with the most basic example of delegate usage, after which I'll write some posts with some more novel ways to leverage them.

If I wrote the following line of code, you'd know what I mean:
int x = 3;

We've declared a variable x and it points to the value "3" on the stack.

If I wrote the next line of code, you'd also know what was meant:
Foo f = new Foo();

We've declared a variable "f" and pointed it to an instance of the class "Foo" on the heap.

I'm stating the obvious because if the previous two lines of code are understandable, you already know the very first thing about delegates: a delegate is a type, just like our primitive int or our class "Foo." The next leap that must be made is that a delegate points to a method somewhere in your code.

Let's expand the definition of the class Foo:

class Foo{
public void FooMeth(){ ... }

Is it possible to make a reference to the instance method FooMeth() somewhere in code? Absolutely, my good man; that's what the delegate is for. To make a reference to this method, first define a delegate somewhere:

public delegate void MethPointer()

Let's break that down: public - access modifier, accessible anywhere, void - this is a delegate that will point to a method that returns no value, MethPointer - this is the name of our delegate, and finally () - the empty parenthesis mean that the delegate will point to a method with no parameters. Delegates are typesafe method pointers - the typesafe part is what you see in the definition: our delegate can only point to a method with an identical return value and parameter list.

So now that we've defined our delegate, how do we create an instance that points to the instance method FooMeth() as we mentioned previously? FooMeth() is an instance method, which means that we need an instance of the class first:

Foo f = new Foo();

Now we create a reference to the delegate in the following manner:

MethPointer d = new MethPointer(f.FooMeth);

We pass the method to the constructor of the delegate. All the hard work is done. To invoke the delegated method, we simply do the following:


One more thing about syntax and then I'll introduce a first basic usage for delegates. If you got this far and you're doing okay, this is a side note you should be able to figure out on your own. A lot of the time, a delegate is declared as a public variable in a class. Something like the following:

public class MyClass{
public delegate void Del();

So the line above when we created a reference to it comes to us often as:

MyClass.Del d = new MyClass.Del(f.FooMeth);

This is just a simple matter of understanding that if we declare something inside a class, we need to use the classname to access it.

Now that we've got the syntax for a basic delegate, how on earth does one use such things?

The example I always like to start with is a "callback": you can call a remote method and pass a reference to a method in your local class. For example, let's say we had a database utility class called DB that had a RunLongQuery method. This method runs a query against our database that sometimes takes 0.0005 seconds, but for large reports it can take 5, 10, sometimes 25 seconds. Whenever the query finishes, you'd like to invoke a method from your application.

So I've got a class MyDatabaseApp which makes a call to RunLongQuery from DB. But we can design RunLongQuery to accept a reference to a method as a parameter by first putting a delegate in DB and then using it as a parameter on the method. Check it out:

public class DB{
public delegate void Done();
public void RunLongQuery(string sql, Done cback){
// code from query
cback(); // invoke the callback method

Let's also look at the internals of the class MyDatabaseApp:

public class MyDatabaseApp{
public void FinishedQuery(){ ... }

public void MyAppQuery(){
DB.Done d = new DB.Done(this.FinishedQuery);
DB dbhelper = new DB();
dbhelper.RunLongQuery("select... ", d);

When MyAppQuery calls the RunLongQuery method from the instance of DB, dbhelper, it passes a delegate reference to the FinishedQuery method. This will automatically be invoked when the query is completed - see inside the definition of DB to see how it's being invoked.

You can download a VS.NET 2003 example of the following here (~15kb).

These are the very beginnings of delegates - our example is also quite trivial because it's just a teaser for one of the more seductive asynchronous callback that can be done with delegates. But by this time, you should be aware that delegates are types - they can be referenced by variables and to create an instance of a delegate we need first to define the delegate and then pass a method that meets its requirements (return type and signature).

Questions? david DOT(.) seruyange AT(@) gmail DOT(.) com


No comments: