Events and Delegates in Depth

Introduction

While browsing through the Net, I came across many articles on events and delegates. But, I did not find any articles that cover most of the contents under one roof. This article is an attempt to do just that.

Many of us working on .NET 1.0 and .NET 1.1 have an idea about what delegates, events, multicast delegates, and so forth are. I’ll give just a brief idea about all these for those who started their career with .NET 2.0 and C# 2.0.

Definition of a Delegate

Delegates can be defined as type safe function pointers and one of the six data types that can directly be declared inside the namespaces. Type safe means the functions that are registered for a particular are bound to have similar signatures like a delegate. Internally, delegates are treated as sealed classes with some predefined methods such as BeginInvoke(), EndInvoke(), GetInvokationList(), and Method as property and so forth. A delegate can be defined as:

public delegate int AddDelegate(int a, int b);

Instantiating a Delegate

Instantiating a delegate means creating an object of a delegate. An object of a delegate can be created by passing the address (name) of the function which is having similar signature like that of delegate. Here, you can instantiate a delegate. You have a function:

Private int AddMe(int a, int b);
AddDelegate add = new Add(AddMe);

Now I can call the AddMe() method by just doing

add(1, 2);

If you see the MSIL of a delegate, it is presented as a class that derives from System.MulticastDelegate, which in turn derives from System.Delegate. You also can use static method “Combine” of the Delegate class to combine more than one delegates.

Multicast Delegates

Multicast delegates can be defined as delegates that have more than one element in their invocation list. In other words, it is a delegate that is subscribed by more than one method.

Here is a simple example.

/************************************************************/
delegate void Del(string s);

class TestClass
{
static void Hello(string s)
{
System.Console.WriteLine(" Hello, {0}!", s);
}

static void Goodbye(string s)
{
System.Console.WriteLine(" Goodbye, {0}!", s);
}

static void Main()
{
Del a, b, c, d;

// Create the delegate object a that references
// the method Hello:
a = Hello;

// Create the delegate object b that references
// the method Goodbye:
b = Goodbye;

// The two delegates, a and b, are composed to form c:
c = a + b;

// Remove a from the composed delegate, leaving d,
// which calls only the method Goodbye:
d = c - a;

System.Console.WriteLine("Invoking delegate a:");
a("A");
System.Console.WriteLine("Invoking delegate b:");
b("B");
System.Console.WriteLine("Invoking delegate c:");
c("C");
System.Console.WriteLine("Invoking delegate d:");
d("D");
}
}

Output
Invoking delegate a:
Hello, A!
Invoking delegate b:
Goodbye, B!
Invoking delegate c:
Hello, C!
Goodbye, C!
Invoking delegate d:
Goodbye, D!

/************************************************************/

If you try to use multicast delegates that return something, the return value of the last registered function will be returned. MulticastDelegate is a special class. Compilers and other tools can derive from this class, but you cannot derive from it explicitly. The same is true of the Delegate class.

A MulticastDelegate has a linked list of delegates, called an invocation list, consisting of one or more elements. When a multicast delegate is invoked, the delegates in the invocation list are called synchronously in the order in which they appear. If an error occurs during execution of the list, an exception is thrown.

Exceptions in Multicast Delegates

Suppose you have added multiple delegates to a single multicast delegate. Each of these individual delegates must be invoked, regardless of whether an unhandled exception is thrown within one of the delegates. But, once a delegate in a multicast delegate throws an unhandled exception, no more delegates are fired. You need a way to trap unhandled exceptions within each individual delegate while still allowing the rest of the delegates to fire.

To avoid breaking the chain, you have to gracefully handle exceptions in all functions. Use the GetInvocationList method. This method returns each individual delegate from a multicast delegate and, by doing so, allows you to invoke each delegate within the try block of an exception handler.

Anonymous Methods in Delegates

Sometimes, you want to use a very small amount of code to call a delegate. Creating functions for such a small code will make the code cumbersome and difficult to read and debug. C# 2.0 comes with a new concept of Anonymous methods. By using anonymous methods, you reduce the overhead in instantiating delegates by eliminating the need of separate method. Here is the small example to illustrate this.

/************************************************************/
     AddDelegate add = delegate (int k) {return a + b;};
/************************************************************/

Covariance and Contravariance in Delegates

These provide a degree of flexibility while matching delegate methods with delegate signatures.

Covariance

Covariance permits a method with a derived return type to be used as a delegate. When a delegate method has a return type that is derived from the return type of delegate, it is called a covariant. Because the return type of method is derived from the return type of delegate, the type conversion is implicit. This enables you to create delegate methods that can be used by base and derived classes.

/************************************************************/
   public class BaseClass
   {
      // Some functions
}

public class DerivedClass : BaseClass
{
   // Some functions
}

public class MainClass
{
   // Define a delegate
   delegate BaseClass TestDelegate();

   private static DerivedClass ReturnDerived()
   {
      return new DerivedClass();
   }

   public static void Main(string []args)
   {
      // Covariance allows this
      TestDelegate delg = ReturnDerived;
   }
}
/************************************************************/

Contravariance:

Contravariance permits a method with derived parameters to be used as a delegate. When a delegate method signature has parameters that are derived from the delegate parameters, the method is said to be contravariant.

/************************************************************/
public class BaseClass
{
   // Some functions
}

public class DerivedClass : BaseClass
{
   // Some functions
}

public class MainClass
{
   // Define a delegate
   delegate BaseClass TestDelegate(BaseClass baseClassArg);

   private static DerivedClass ReturnDerived(DerivedClass dr)
   {
      return dr;
   }

   public static void Main(string []args)
   {
      // Contravariance allows this
      TestDelegate delg = ReturnDerived;
   }
}
/************************************************************/

More by Author

Must Read