Abstract
In this article, you will learn how to create and manipulate delegate types as well as C# events that streamline the process of working with delegate types.
Delegates provide a way to define and execute callbacks. Their flexibility allows you to define the exact signature of the callback, and that information becomes part of the delegate type itself. Delegates are type-safe, object-oriented and secure.
Delegates Overview
A Delegate is an abstraction of one or more function pointers (as existed in C++; the explanation about this is out of the scope of this article). The .NET has implemented the concept of function pointers in the form of delegates. With delegates, you can treat a function as data. Delegates allow functions to be passed as parameters, returned from a function as a value and stored in an array. Delegates have the following characteristics:
- Delegates are derived from the System.MulticastDelegate class.
- They have a signature and a return type. A function that is added to delegates must be compatible with this signature.
- Delegates can point to either static or instance methods.
- Once a delegate object has been created, it may dynamically invoke the methods it points to at runtime.
- Delegates can call methods synchronously and asynchronously.
The delegate contains a couple of useful fields. The first one holds a reference to an object, and the second holds a method pointer. When you invoke the delegate, the instance method is called on the contained reference. However, if the object reference is null then the runtime understands this to mean that the method is a static method. Moreover, invoking a delegate syntactically is the exact same as calling a regular function. Therefore, delegates are perfect for implementing callbacks.
Why Do We Need Delegates
Historically, the Windows API made frequent use of C-style function pointers to create callback functions. Using a callback, programmers were able to configure one function to report back to another function in the application. So the objective of using a callback is to handle button-clicking, menu-selection, and mouse-moving activities. But the problem with this traditional approach is that the callback functions were not type-safe. In the .NET framework, callbacks are still possible using delegates with a more efficient approach. Delegates maintain three important pieces of information, as in the following;
- The parameters of the method.
- The address of the method it calls.
- The return type of the method.
A delegate is a solution for situations in which you want to pass methods around to other methods. You are so accustomed to passing data to methods as parameters that the idea of passing methods as an argument instead of data might sound a little strange. However, there are cases in which you have a method that does something, for instance, invoking some other method. You do not know at compile time what this second methods is. That information is available only at runtime hence Delegates are the device to overcome such complications.
Defining a Delegate
A Delegate can be defined as a delegate type. Its definition must be similar to the function signature. A delegate can be defined in a namespace and within a class. A delegate cannot be used as a data member of a class or local variable within a method. The prototype for defining a delegate type is as the following;
accessibility delegate return type delegatename(parameterlist);
Delegate declarations look almost exactly like abstract method declarations, you just replace the abstract keyword with the delegate keyword. The following is a valid delegate declaration:
- public delegate int operation(int x, int y);
When the C# compiler encounters this line, it defines a type derived from MulticastDelegate, that also implements a method named Invoke that has exactly the same signature as the method described in the delegate declaration. For all practical purposes, that class looks like the following:
- public class operation : System.MulticastDelegate
- {
- public double Invoke(int x, int y);
-
- }
As you can see using ILDASM.exe, the compiler generated operation class defines three public methods as in the following:
Figure 1.1
After you have defined a delegate, you can create an instance of it so that you can use it to store the details of a particular method.
Delegates Sample Program
The delegate implementation can cause a great deal of confusion when encountered the first time. Thus, it is a great idea to get an understanding by creating this sample program as:
- using System;
-
- namespace Delegates
- {
-
- public delegate int operation(int x, int y);
-
- class Program
- {
-
-
- static int Addition(int a, int b)
- {
- return a + b;
- }
-
- static void Main(string[] args)
- {
-
- operation obj = new operation(Program.Addition);
-
-
- Console.WriteLine("Addition is={0}",obj(23,27));
- Console.ReadLine();
- }
- }
- }
Here, we are defining the delegate as a delegate type. The important point to remember is that the signature of the function reference by the delegate must match the delegate signature as:
-
- public delegate int operation(int x, int y);
Notice the format of the operation delegate type declaration; it specifies that the operation object can permit any method taking two integers and returning an integer. When you want to insert the target methods to a given delegate object, simply pass in the name of the method to the delegate constructor as:
-
- operation obj = new operation(Program.Addition);
At this point, you are able to invoke the member pointed to using a direct function invocation as:
- Console.WriteLine("Addition is={0}",obj(23,27));
Finally, when the delegate is no longer required, set the delegate instance to null.
Recall that .NET delegates are type-safe. Therefore, if you attempt to pass a delegate a method that does not match the signature pattern, the .NET will report a compile-time error.
Array of Delegates
Creating an array of delegates is very similar to declaring an array of any type. The following example has a couple of static methods to perform specific math related operations. Then you use delegates to call these methods.
- using System;
-
- namespace Delegates
- {
- public class Operation
- {
- public static void Add(int a, int b)
- {
- Console.WriteLine("Addition={0}",a + b);
- }
-
- public static void Multiple(int a, int b)
- {
- Console.WriteLine("Multiply={0}", a * b);
- }
- }
-
- class Program
- {
- delegate void DelOp(int x, int y);
-
- static void Main(string[] args)
- {
-
- DelOp[] obj =
- {
- new DelOp(Operation.Add),
- new DelOp(Operation.Multiple)
- };
-
- for (int i = 0; i < obj.Length; i++)
- {
- obj[i](2, 5);
- obj[i](8, 5);
- obj[i](4, 6);
- }
- Console.ReadLine();
- }
- }
- }
In this code, you instantiate an array of Delop delegates. Each element of the array is initialized to refer to a different operation implemented by the operation class. Then, you loop through the array, apply each operation to three different values. After compiling this code, the output will be as follows;
Figure 1.2
Anonymous Methods
Anonymous methods, as their name implies, are nameless methods. They prevent creation of separate methods, especially when the functionality can be done without a new method creation. Anonymous methods provide a cleaner and convenient approach while coding.
Define an anonymous method with the delegate keyword and a nameless function body. This code assigns an anonymous method to the delegate. The anonymous method must not have a signature. The signature and return type is inferred from the delegate type. For example if the delegate has three parameters and return a double type, then the anonymous method would also have the same signature.
- using System;
-
- namespace Delegates
- {
- class Program
- {
-
- delegate void operation();
-
- static void Main(string[] args)
- {
-
- operation obj = delegate
- {
- Console.WriteLine("Anonymous method");
- };
- obj();
-
- Console.ReadLine();
- }
- }
- }
The anonymous methods reduce the complexity of code, especially where there are several events defined. With the anonymous method, the code does not perform faster. The compiler still defines methods implicitly.
Multicast Delegate
So far, you have seen a single method invocation/call with delegates. If you want to invoke/call more than one method, you need to make an explicit call through a delegate more than once. However, it is possible for a delegate to do that via multicast delegates.
A multicast delegate is similar to a virtual container where multiple functions reference a stored invocation list. It successively calls each method in FIFO (first in, first out) order. When you wish to add multiple methods to a delegate object, you simply make use of an overloaded += operator, rather than a direct assignment.
- using System;
-
- namespace Delegates
- {
- public class Operation
- {
- public static void Add(int a)
- {
- Console.WriteLine("Addition={0}", a + 10);
- }
- public static void Square(int a)
- {
- Console.WriteLine("Multiple={0}",a * a);
- }
- }
- class Program
- {
- delegate void DelOp(int x);
-
- static void Main(string[] args)
- {
-
- DelOp obj = Operation.Add;
- obj += Operation.Square;
-
- obj(2);
- obj(8);
-
- Console.ReadLine();
- }
- }
- }
The code above combines two delegates that hold the functions Add() and Square(). Here the Add() method executes first and Square() later. This is the order in which the function pointers are added to the multicast delegates.
To remove function references from a multicast delegate, use the overloaded -= operator that allows a caller to dynamically remove a method from the delegate object invocation list.
-
- DelOp obj = Operation.Add;
- obj -= Operation.Square;
Here when the delegate is invoked, the Add() method is executed but Square() is not because we unsubscribed it with the -= operator from a given runtime notification.
Invoking multiple methods by one delegate may lead into a problematic situation. If one of the methods invoked by a delegate throws an exception, then the complete iteration would be aborted. You can avoid such a scenario by iterating the method invocation list on your own. The Delegate class defines a method GetInvocationList that returns an array of Delegate objects.
- using System;
-
- namespace Delegates
- {
- public class Operation
- {
- public static void One()
- {
- Console.WriteLine("one display");
- throw new Exception("Error");
- }
- public static void Two()
- {
- Console.WriteLine("Two display");
- }
- }
-
- class Program
- {
- delegate void DelOp();
-
- static void Main(string[] args)
- {
-
- DelOp obj = Operation.One;
- obj += Operation.Two;
-
- Delegate[] del = obj.GetInvocationList();
-
- foreach (DelOp d in del)
- {
- try
- {
- d();
- }
- catch (Exception)
- {
- Console.WriteLine("Error caught");
- }
- }
- Console.ReadLine();
- }
- }
- }
When you run the application, you will see that the iteration still continues with the next method even after an exception is caught as in the following.
Figure 1.3
Events
The applications and windows communicate via predefined messages. These messages contain various pieces of information to determine both window and application actions. The .NET considers these messages as an event. If you need to react to a specific incoming message then you would handle the corresponding event. For instance, when a button is clicked on a form, Windows sends a WM_MOUSECLICK message to the button message handler.
You don't need to build custom methods to add or remove methods to a delegate invocation list. C# provides the event keyword. When the compiler processes the event keyword, you can subscribe and unsubscribe methods as well as any necessary member variables for your delegate types. The syntax for the event definition should be as in the following:
Accessibility event delegatename eventname;
Defining an event is a two-step process. First, you need to define a delegate type that will hold the list of methods to be called when the event is fired. Next, you declare an event using the event keyword. To illustrate the event, we are creating a console application. In this iteration, we will define an event to add that is associated to a single delegate DelEventHandler.
- using System;
-
- namespace Delegates
- {
- public delegate void DelEventHandler();
-
- class Program
- {
- public static event DelEventHandler add;
-
- static void Main(string[] args)
- {
- add += new DelEventHandler(USA);
- add += new DelEventHandler(India);
- add += new DelEventHandler(England);
- add.Invoke();
-
- Console.ReadLine();
- }
- static void USA()
- {
- Console.WriteLine("USA");
- }
-
- static void India()
- {
- Console.WriteLine("India");
- }
-
- static void England()
- {
- Console.WriteLine("England");
- }
- }
- }
In the main method, we associate the event with its corresponding event handler with a function reference. Here, we are also filling the delegate invocation lists with a couple of defined methods using the +=operator as well. Finally, we invoke the event via the Invoke method.
Note: Event Handlers can't return a value. They are always void.
Let's use another example to get the better understanding of events. Here we are defining an event name as xyz and a delegate EventHandler with a string argument signature in the operation class. We are putting the implementation in the action method to confirm whether an event is fired or not.
- using System;
-
- namespace Delegates
- {
- public delegate void EventHandler(string a);
-
- public class Operation
- {
- public event EventHandler xyz;
-
- public void Action(string a)
- {
- if (xyz != null)
- {
- xyz(a);
- Console.WriteLine(a);
- }
- else
- {
- Console.WriteLine("Not Registered");
- }
- }
- }
-
- class Program
- {
- public static void CatchEvent(string s)
- {
- Console.WriteLine("Method Calling");
- }
-
- static void Main(string[] args)
- {
- Operation o = new Operation();
-
- o.Action("Event Calling");
- o.xyz += new EventHandler(CatchEvent);
-
- Console.ReadLine();
- }
- }
- }
Later in the Program class, we are defining a CatchEvent method that would be referenced in the delegate invocation list. Finally, in the main method, we instantiated the operation class and put in the event firing implementation.
Finally we are elaborating on the events with a realistic example of creating a custom button control over the Windows form that would be called from a console application. First, we inherit the program class from the Form class and define its associated namespace at the top. Thereafter, in the program class constructor, we are crafting a custom button control.
- using System;
- using System.Drawing;
- using System.Windows.Forms;
-
- namespace Delegates
- {
-
- public delegate void DelEventHandler();
-
- class Program :Form
- {
-
- public event DelEventHandler add;
-
- public Program()
- {
-
- Button btn = new Button();
- btn.Parent = this;
- btn.Text = "Hit Me";
- btn.Location = new Point(100,100);
-
-
-
- btn.Click += new EventHandler(onClcik);
- add += new DelEventHandler(Initiate);
-
-
- add();
- }
-
- public void Initiate()
- {
- Console.WriteLine("Event Initiated");
- }
-
-
- public void onClcik(object sender, EventArgs e)
- {
- MessageBox.Show("You clicked me");
- }
- static void Main(string[] args)
- {
- Application.Run(new Program());
-
- Console.ReadLine();
- }
- }
-
- }
We are calling the Windows Form by using the Run method of the Application class. Finally when the user runs this application, first the Event fired message will be displayed on the screen and a Windows Form with the custom button control. When the button is clicked, a message box will be shown as in the following;
Figure 1.4