Delegate events!

This article was originally published in VSJ, which is now part of Developer Fusion.
It’s long been a truism that the way to get work done is to delegate, but what are C# delegates all about? They seem to be just a complicated way of calling a method that you could just as easily call in the usual way. Of course the key is that a delegate really does delegate – it allows others to call a method on your behalf. Let’s look at how it works and some of the more interesting ways that you can put it to work.

Delegate basics

What is initially confusing is that to create a delegate you first have to create a type and then create an instance of the type. That is, a delegate is a user-defined reference type that encapsulates a method. Consider, for example, how to encapsulate the method:
public int hello(int param1)
{
	MessageBox.Show("Hello delegate World"
		+param1.ToString());
	return(++param1);
}
First we need to define a delegate type that matches its signature – including, in this case, the return type:
delegate int HelloType(int param);
This delegate type defines the methods that it can encapsulate.

Next we have to create an instance of the type and supply it with the hello method to encapsulate:

HelloType HelloInst=	new HelloType(hello);
If you prefer you can use the overloaded assignment operator:
HelloType HelloInst = hello;
…to create an instance.

Now we can run the original Hello method by calling it directly in the usual way:

int i = hello(2);
…or by using the delegate’s invoke method:
int i=HelloInst.Invoke(2);
…or by calling the delegate instance as if it was the hello method:
int i=HelloInst(2);
This last form is just a convenience as it implicitly uses the Invoke method. Invoke uses the same current thread to run the delegate and in this case you need to be aware of any potential “cross threading” problems that might arise. This also means that the invocation is asynchronous and the calling code will wait until the delegate returns. You can invoke the delegate asynchronously using a thread from the thread pool using BeginInvoke, or you could create a thread manually to run the delegate.

Why would you use a delegate rather than just calling a method? The answer is simply that a delegate can be passed as a parameter to another method, so determining what method is called at run time. There are two well-known patterns that demand the use of a delegate. The first is the “callback” or notification method which is supplied to an object for it to call with intermediate or final results of its working. Of course in this instance the object is usually run on a separate thread and the callback/notification method provides some asynchronous communication between the caller and the called thread.

The second well-known pattern is event handling. A delegate can be set up within and object so that clients can provide a method to be called when an event occurs. If you think carefully you will see that there is little difference between the callback and the event pattern. In both cases a delegate is called when some condition occurs – a buffer is full, the user has clicked a button, an error condition has been detected, etc. However, while events are based on delegates they add some additional structure – an add and remove accessor similar to a property.

A third, slightly less common, use is in creating a new thread of execution – see my article Deep threading – the gentle use of invoke.

At this point it is worth making clear that the delegate’s signature always determines how the delegate is called. That is, you always have to invoke the delegate with the parameter types specified, and the delegate always returns the type specified. However, it is possible to create instances of the delegate that encapsulate methods that don’t have exactly the specified signature.

So to summarise:

  • A delegate type’s signature specifies how the delegate is invoked – i.e. the parameters and return type are always given by the delegate’s signature.
  • A delegate type’s signature specifies what sort of methods an instance of the type can encapsulate.
Notice that the encapsulated method can be a static or an instance method. The Target property stores the instance and the Method property stores the method that that the delegate encapsulates. If the delegate encapsulates a static method then Target is null. To encapsulate a method on a specific object you simply have to qualify the method name with the object’s name e.g. this.hello or MyObject.hello.

What methods?

In the simplest case a method’s signature and return type have to match the signature of the delegate type that encapsulates it. However, there is more flexibility in how a method signature can match a delegate type signature. In the documentation this is called covariance and contravariance – just to make it sound more sophisticated.

Put simply, covariance allows the method to return a sub-class or derived type of the return type defined in the delegate. Assume for the moment that MyType1 is the base class and MyType2 is the derived class, that is:

public class MyType1 {};
public class MyType2 : MyType1 {};
If you now define the delegate type:
delegate MyType1
	HelloType(int param);
…then clearly a method returning MyType1 matches the signature, but so does any method returning a type that inherits from MyType1, e.g. MyType2. So the delegate type can encapsulate a method that returns a MyType2. But following the rule that the delegate signature determines its invocation, the return type is always treated as MyType1. If the method does return a derived type, MyType2 say, you have to use a cast to work with the result as a MyType2 object.

Contravariance allows the method to have parameters that are base types of the delegate type that encapsulates it. That is, if you define the delegate type:

delegate void HelloType(MyType2 param);
…then a method that that has a parameter that is a base class for MyType2, e.g. MyType1, matches the signature. Once again you can only invoke the delegate by passing a MyType2 object, but this all works because the method can always treat this as a more primitive type, e.g. as a MyType1 object.

Generic delegates

To confuse matters even more, or should that be to confer further elegance and power, you can also create generic delegate types. Put simply you can use a generic type anywhere within a delegate definition. For example:
delegate int HelloType<T>(T param);
…creates a delegate type with a generic parameter. To use the delegate you have to provide the type information, for example:
HelloType<int> HelloInst =
	new HelloType<int>(hello);
…where Hello is a method which returns an int and has a single int parameter. You can also use the shorter:
HelloType<int> HelloInst = hello;
Before you start to invent clever ways of creating generic delegates for every purpose, I should warn you that in the main generic delegates should only be used within generic classes. In this context they provide a way to create delegate instances that “fit in” with the functioning of the entire delegate class. For example, you might create a delegate as a notification or event method which can be customised to work with the same data types as the rest of the generic class.

Anonymous methods

As of .NET 2.0 you can save some typing and names by using anonymous, as opposed to named, methods. However as of .NET 3.5 anonymous methods are superseded by lambda expression. Put simply using an anonymous method you can combine the definition of the method with creating an instance of the delegate type using an anonymous method:
HelloType HelloInst2 = delegate(
	int param1)
{
MessageBox.Show("Hello delegate World"
	+param1.ToString());
	return(param1+1);
};
Notice that you don’t have to specify the return type, but you do have to specify any parameters used. If an anonymous method doesn’t have anything to return or doesn’t have any out parameters then you can also drop the parameter list as in:
delegate void HelloType(int param);
HelloType HelloInst3 = delegate
{
MessageBox.Show("Hello delegate World");
};
But, in line with the rule that the delegate’s signature controls how it is invoked, you do have to pass the parameter when the delegate is called:
HelloInst3(3);
Also notice that you can’t use an anonymous method to define a contravariant method; in this case type checking is enforced and the method and delegate parameter signatures must match.

Of additional interest is the way an anonymous method enforces closure, but you can find out about this subject in my article Anonymous methods and closure.

Lambda expressions

Lambda expressions sound esoteric; the name comes from lambda calculus a branch of computer science not much visited these days. In practice, however, lambda expressions are simply a more concise way of writing an anonymous method. The basic syntax is:
(comma separated parameters)=>
	{semicolon terminated statement list;}
The => symbol is read “becomes” to indicate that the parameters are transformed into the actions. A lambda expression shares all its characteristics with anonymous methods. For example, to define the Hello anonymous method listed earlier using a lambda expression you would write something like:
delegate int HelloType(int param);
HelloType HelloInst = (int param1) =>
{
	MessageBox.Show("Hello delegate World"
		+ param1.ToString());
	return ++param1;
};
Once the lambda expression is encapsulated by the delegate instance everything works in the same way. Notice that lambda expressions, like anonymous methods, “capture” the local variables in the same scope as themselves. This means that they too implement closure and the examples given in the earlier article work just as well with the delegates being defined using a lambda expression as with an anonymous method.

There are some tricks that you can use with lambda expressions to make the method definition even more concise and flexible. The first is that the compiler can deduce the type of the parameters from the delegate type. Essentially the lambda expression has to have the same number of parameters as the delegate and the return type, if any, has to be implicitly convertible to the return type of the delegate. Thus the previous lambda expression can be written:

HelloType HelloInst = (param1) =>
{
	MessageBox.Show("Hello delegate World"
		+ param1.ToString());
	return ++param1;
};
It is also worth knowing that there is an even simpler form of lambda expression that consists of a single expression returning a result. For example:
x => x <= 10;
…defines a lambda expression that returns true if x is smaller than or equal to 10 and false otherwise. Such simple lambda expressions can be converted to delegates or expression trees – but that’s another story. Expression trees are mostly used with LINQ and SQL coding. Notice that lambda expressions are ideal for passing short chunks of code, Boolean conditionals for example, to other methods which is what they were designed for.

This is all there is to lambda expression – nothing more is hidden in the technical sounding name. You can use anonymous methods or lambda expressions to the same end – but notice that the Microsoft documentation treats lambda expressions as superseding anonymous methods.

Multicasting

If you look at the Framework Reference you will find a Delegate class and a class derived from it, MulticastDelegate. In theory the Delegate class can encapsulate a single method and the MulticastDelegate maintains a list of methods and so can encapsulate more than one method. In practice this isn’t true. Early on in the development of the framework it was decided that Delegate and MulticastDelegate should do the same job and be merged. However merging them would have created too many problems so Delegate was left as the base class for MulticastDelegate, but the two are essentially identical.

At this point you might imagine that you would never use the Delegate class but in fact it is still returned by some methods that expect to return a single method encapsulated by a Delegate. It also used to be the case that if you created a delegate which returned void then it was encapsulated by a MulticastDelegate but if it returned a result then a Delegate was used. This distinction was made because of the difficult of determining which method’s return value was actually returned in a multicast invocation. However, this was solved by simply encapsulating all methods using MutlicastDelegate and in a multicast situation the last method called is the only one that returns a result. All delegates are MulticastDelegates but for efficiency reasons an invocation list is only created if there is more than one method encapsulated. That is, a delegate that encapsulates a single method stores this method in its Method and Target properties and has a null invocation list. Both of these classes are abstract and you cannot instantiate either directly – but this doesn’t mean that they aren’t of use as is explained below.

In normal use it is up to the compiler to create instances of a delegate class appropriate to encapsulate a method. Exactly how this is done can vary according to the .NET language in use. In C# the delegate keyword creates an instance not of the Delegate class but of the MulticastDelegate class. That is, in C# an instance of a delegate type is always an instance of a MulticastDelegate which extends the basic Delegate class to include an Invocation list which can store multiple methods. In other words, a MulticastDelegate can encapsulate a list of methods.

The easiest way to follow this confusing situation is to try it all out. First we need two methods with the same signature that we can encapsulate in a suitable delegate:

public int hello(string param)
{
	MessageBox.Show("Hello "+param);
	return 1;
}

public int goodbye(string param)
{
	MessageBox.Show("Goodbye " + param);
	return 2;
}
A suitable delegate type is just:
delegate int greetType(string param);
Now we can create an instance of MulticastDelegate encapsulating two methods:
greetType DoGreet = hello;
DoGreet += goodbye;
The += operator is overloaded to add a method to the invocation list and this should be familiar to you if you have hand-coded events. Invoking the delegate is just the same if it has single or multiple methods:
int i= DoGreet("delegates");
If the invocation list contains multiple methods then these are called in the order that they were added. Any parameters are passed to each method in turn and the final method to be called returns a value. That is, in this case the Hello method is called with its parameter set to “delegates” then Goodbye is called with the same parameter. Only Goodbye returns a result and hence i is set to 2.

You can use anonymous methods or lambda expressions to define delegates within multicast delegates. For example:

DoGreet += delegate(string param)
{
	MessageBox.Show("Goodbye " + param);
	return 2;
};
Or, more properly if you agree that lambda expressions should be used in preference to anonymous methods:
DoGreet += (string param)=>
{
	MessageBox.Show("Goodbye " + param);
	return 2;
};
You may well know the += operator for adding to the invocation list but there is also a -= that can be used to remove methods. In this case -=b will remove the last occurrence (in execution order) of the delegate that is equal to b. Equality between delegates is defined so that equal delegates have to be of the same type and encapsulate the same method – Target and Method. Notice that this is a change from version 1 of C# where equal delegates could be different types. Also notice that two MulticastDelegates are considered equal if their invocation lists are identical and in the same order.

Delegates don’t provide direct access to their invocation list and you can’t manipulate it directly. However you can retrieve the invocation list as an array of Delegates. For example:

Delegate[] InvList =
	DoGreet.GetInvocationList();
…returns a delegate array with two elements – a delegate wrapping Hello and a delegate wrapping Goodbye. You can even invoke each delegate in the invocation list by enumerating the array:
foreach (Delegate D in InvList)
{
	D.DynamicInvoke("Loop");
}
You might be puzzled as to why we use DynamicInvoke rather than Invoke? The reason is that neither Delegate nor MulticastDelegate support an Invoke method. When you use the classes directly in this way the invocation of the method cannot be checked at compile time, hence the need for a “DynamicInvoke”.

Some of the simplicity of using a MuticastDelegate comes from the overloading of the += and -= operators but there are other, perhaps surprising ways, that you can work with invocation lists. For example, you can create a delegate that has the combined invocation list of other delegates using the “+” operator:

greetType DelA = hello;
greetType DelB = goodbye;
greetType DelC;
DelC = DelA + DelB;
In this case DelC encapsulates the methods of DelA and DelB. Similarly the “-” operator removes one delegate’s invocation list from another. For example:
DelC = DelC - DelA;
…removes all of the delegates in the invocation list of DelA from DelC. It is also worth knowing that there are static methods defined on the Delegate class such as Combine and Remove which can be used to manipulate arrays of Delegates as well as pairs of Delegates.

Events

A common misconception is that .NET events are just multicast delegates. This is mostly true, but an event has some additional infrastructure: it is a multicast delegate with the addition of two accessor functions, add and remove, that take over the adding and removal of delegates from the invocation list. Most of the time you can ignore the existence of the accessor functions as the system will provide default implementations for you and, as the += and -= operators are overloaded and call these default accessor functions, you can use an event as if it was a simple delegate. The basic idea is that a class can provide an event that any clients can subscribe to simply by adding delegates to the invocation list of the event’s multicast delegate. The event can only be “raised” by the class that owns the event and this results in each delegate in the invocation list being called.

Events are mostly generated automatically for us by Visual Studio, but to really understand what is going on you can’t do better than implement a custom event manually. First we need a suitable delegate type:

public delegate int MyNewEventType(
	string param);
This we convert into an event:
public event MyNewEventType MyNewEvent;
MyNewEvent is now an event instance which wraps an anonymous instance of the delegate type. We can add a delegate to the event in the usual way:
MyNewEvent += new MyNewEventType(hello);
You can also use anonymous methods and lambda expressions to add delegates to an event:
MyNewEvent += (string param) =>
{
	MessageBox.Show("Goodbye " + param);
	return 2;
};
To raise the event, we simply call it as if it was a delegate:
MyNewEvent("New Event");
This is slightly more subtle than it looks because, for a default event with no custom accessor functions, the compiler changes the reference to the event to a reference to the private internal instance of the delegate. This allows you to call the delegate using the name of the event – but if you do define custom accessor functions this no longer works.

You can use Invoke if you want to, but notice that an event restricts the invocation of its delegate to the object in which it is declared. In other words, an object cannot raise another’s event. The new event object has all of the methods of a delegate and can be used more or less as if it was a delegate. In particular, you can use BeginInvoke to fire an event asynchronously on a threadpool thread. If you are concerned about using up threadpool threads you could even create a thread manually and run the delegate using it. By default an event delegate is called synchronously and this means that the code that raises the event is stalled until the event processing is complete. Are asynchronous events a good idea? If you want to write a responsive program, then yes they are, but you need to understand the problems that can arise in a multithreaded approach.

Even if you don’t explicitly make use of a multithreaded approach you have to allow for the possibility that your code will be used in this way. For example, if you want to provide a custom add and remove accessor then you need to code the event something like:

private MyNewEventType _MyNewEvent;
public event MyNewEventType MyNewEvent
{
	add
	{
		lock(this)
		{
			_MyNewEvent += value;
		}
	}

	remove
	{
		lock(this)
		{
			_MyNewEvent -= value;
		}
	}
}
Notice that you need to use a lock to stop other threads from updating the delegate while you are in the middle of doing so. Locking on the current object, i.e. this, isn’t a good idea because other threads might well try to acquire a lock on some other resource using it, so producing unnecessary waiting for locks to be released. Notice also that you now have to provide a private member variable to store the delegate. You can use MyNewEvent because using += on it would trigger the accessor functions in an infinite recursion. As the compiler now has no idea what you have called this internal private variable, you can no longer raise the event using the event’s name. The standard solution is to provide an “On” method something like:
private void OnMyNewEvent(string param)
{
	if (_MyNewEvent != null)
	{
		_MyNewEvent(param);
	}
}
Following this pattern means always calling the On method to raise the event. This is the pattern used automatically for all built-in events within the .NET Framework. Notice, however, that this isn’t threadsafe because you could check that the invocation list isn’t null and start the call when another thread takes over and changes the invocation list! Again a correct solution is to obtain a lock before processing the invocation list.

In short, events are inherently multithreading and as such are dangerous.


Dr. Mike James’ programming career has spanned many languages, starting with Fortran. The author of Foundations of Programming, he has always been interested in the latest developments in programming and the synergy between different languages.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“We better hurry up and start coding, there are going to be a lot of bugs to fix.”