In the early days of computing, a program would begin execution and then proceed through its steps until it completed. If the user was involved, the interaction was strictly controlled and limited to filling in fields.
Today's Graphical User Interface (GUI) programming model requires a different approach, known as event-driven programming. A modern program presents the user interface and waits for the user to take an action. The user might take many different actions, such as choosing among menu selections, pushing buttons, updating text fields, clicking icons, and so forth. Each action causes an event to be raised. Other events can be raised without direct user action, such as events that correspond to timer ticks of the internal clock, email being received, file-copy operations completing, etc. In programming, you are often faced with situations where you need to execute a particular action, but you don't know in advance which method, or even which object, you'll want to call upon to execute it. For example, a button might know that it must notify some object when it is pushed, but it might not know which object or objects need to be notified.
C# adds on value to the often mentioned world of event driven programming by adding support through events and delegates.
Events
An event in is a way for a class to provide notifications to clients of that class when some interesting thing happens to an object. The most familiar use for events is in graphical user interfaces; typically, the classes that represent controls in the interface have events that are notified when the user does something to the control (for example, click a button). An event is the outcome of an action. There are two important terms with respect to events. The event source and the event receiver. The object that raises the event is called event source and the object that responds to the event is called event receiver. The communication channel between an event source and an event receiver is the delegate.
Delegate
Delegates act as an intermediary between an event source and an event destination. Technically, a delegate is a reference type used to encapsulate a method with a specific signature and return type. You can encapsulate any matching method in that delegate. To be even precise delegates are similar to function pointers. They can be called as type safe function pointers. Unlike function pointers, delegates are object-oriented and type safe. A delegate class used as an intermediary between an event source and event receiver is called an event handler.
There are three steps in defining and using delegates:
- Declaration
- Instantiation
- Invocation
Declaration
A delegate is created with the delegate keyword, followed by a return type and the signature of the methods that can be delegated to it, as in the following:
public
delegate int MyDelegate(object obj1, object obj2);
This declaration defines a delegate named MyDelegate, which will encapsulate any method that takes two objects as parameters and that returns an int. Once the delegate is defined, you can encapsulate a member method with that delegate by instantiating the delegate, passing in a method that matches the return type and signature. The delegate can then be used to invoke that encapsulated method.
Instantiation
In order to make use of this delegate, you need to instantiate it, to specify the method that needs to be called.
public
void MyMethod()
{
MyDelegate a = new MyDelegate(MyDelegateMethod);
}
Here MyDelegateMethod is a method that has a signature similar to that of MyDelegate.
If signatures of method and delegate do not match, the C# compiler reports an error such as:
td.cs(38,46): error CS0123: Method DelegateDemo.SampleClient.MyEvent()'
does not match delegate 'void DelegateDemo.CustomTextEvent()'
Invocation
A delegate is used to invoke a method similar to how a method call is made.
For example: MyDelegateMethod("This is a test invocation");
Events and Delegates
Declaring an event is directly tied to a delegate. A delegate object encapsulates a method so that it can be called anonymously. An event is a mechanism by which a client class can pass in delegates to methods that need to be invoked whenever "something happens". When it does, the delegate(s) given to it by its clients are invoked.
To declare an event in C#, use the following syntax:
public delegate void testDelegate(int a);
public event testDelegate MyEvent;
Once an event is declared, it must be associated with one or more event handlers before it can be raised. An event handler is nothing but a method that is called using a delegate. Use the += operator to associate an event with an instance of a delegate that already exists.
For example:
Myform.MyEvent +=
new testEvent(MyMethod);
An event handler may also be detached as follows:
MyForm.MyEvent -=
new testEvent(MyMethod);
In C#, events may be raised by just calling them by name similar to method invocation, say MyEvent(10).
How Event works?
Whenever an event is defined for a class, the compiler generates three methods that are used to manage the underlying delegate:
add_<EventName> :
this is a public method that calls the static Combine method of System.Delegate in order to add another method to its internal invocation list. This method is however not used explicitly. The same effect is achieved by using the += operator as specified before.
remove_<EventName> :
this is also a public method that calls the static Remove method of System.Delegate in order to remove a receiver from the event's invocation list. This method is also not called directly. Its job is done by the "-=" operator.
raise_<EventName> :
a protected method that calls the compiler generated Invoke method of the delegate, in order to call each method in the invocation list.
Types of Delegates
There are basically two types of delegates. Single Cast delegate and Multi-Cast delegate. A single cast delegate can call only one function. A multi-cast delegate is one that can be part of a linked list. The multi-cast delegate points to the head of such a linked list. This means that when the multi-cast delegate is invoked it can call all the functions that form a part of the linked list. Assume that one has several clients who would like to receive notification when a particular event occurs. Putting all of them in a multi-cast delegate can help call all the clients when a particular event occurs.
To support a single cast delegate the base class library includes a special class type called System.Delegate. To support multi-cast delegates the base class library includes a special class type called SystemMultiCastDelegate.
Single Cast Delegate
The signature of a single cast delegate is shown below:
public
delegate Boolean DelegateName (parm1, parm2)
The names param1 and param2 can be replaced with one's own names and parameters. When the compiler compiles the statement above, it internally generates a new class type. This class is called DelegateName and derives from System.Delegate.
public
delegate Boolean MyDelegate(Object sendingobj, Int32 x);
public class TestDelegateClass
{
Boolean MyFunction(Object sendingobj, Int32 x)
{
//Perform some processing
return (true);
}
public static void main(String [] args)
{
//Instantiate the delegate passing the method to invoke in its
//constructor
MyDelegate mdg = new MyDelegate(MyFunction);
// Construct an instance of this class
TestDelegateClass tdc = new TestDelegateClass();
// The following line will call MyFunction
Boolean f = mdg(this, 1);
}
}
Lest see how the above code works.
The System.Delegate contains a few fields. The most important of them are Target and Method.
The Target field identifies an object context. In the scenario above this would point to the TestDelegateClass being created. If the method to be invoked is a static method then this field will be null.
The Method field identifies the method to be called. This field always has some value. It is never null.
In our case we are passing only one parameter. The natural question comes: How this is done? Actually it gets done internally by the compiler. The compiler resolves the call above to a call passing in the two parameters required. In C++ with managed extension, the object and the address of the function have to be passed explicitly. But in VB and C# we have done away with this with the compiler filling in the necessary details.
In the code sample above we saw:
Boolean f = mdg(
this, 1);
When the compiler comes across this line, this is what happens,
- The compiler identifies that mdg is a delegate object.
- The delegate object has the target and method fields as described above.
- The compiler generates code that calls the Invoke method of the System.Delegate derived class i.e MyDelegate.
- The Invoke method internally uses the MethodInfo type identified by the delegate's Method field and calls the MethoidInfo's invoke method.
- The MethodInfo's invoke method is passed the delegates Target field which identifies the method and an array of variants which contains the parameters to the method to be called. In our case, the array of variants would be an Object and an Int32 value.
Multi-Cast Delegates
The signature of a mutli cast delegate is shown below. The letters in italics can be replaced with your own names and parameters.
public
delegate void DelegateName (parm1, parm2)
Whatever has been written with respect to Single cast delegate holds good even in the context of a Multi-Cast Delegate. There is a small addition here. Since a multi-cast delegate represents a linked list, there is an additional field called prev which refers to another Multi-Cast Delegate. This is how the linked list is maintained.
The return type here has changed from Boolean to void. The reason is that since several multi-cast delegates get called consecutively we cannot wait to get the return value from each of these methods being called.
The code shown in the example for single cast delegate will work in the case of multi-cast delegate too. The only difference is that the delegate signature needs to be changed, the method signature needs to be changed to return void instead of Boolean.
The power of multi-cast delegates comes in when we combine delegates to form a linked list. The Combine method is available in the System.Delegate class as a static method. The function signature as follows:
public
static Delegate Combine(Delegate a, Delegate b);
Code Sample:
class
MyDelegate1
{
public void dispMyDelegate1(string s)
{
Console.WriteLine("MyDelegate1");
}
}
class MyDelegate2
{
public void dispMyDelegate2(string s)
{
Console.WriteLine("MyDelegate2");
}
}
public delegate void OnMsgArrived(string s);
class TestMultiCastUsingDelegates
{
public static void Main(string [] args)
{
MyDelegate1 mcd1=new MyDelegate1();
MyDelegate2 mcd2=new MyDelegate2();
// Create a delegate to point to dispMyDelegate1 of mcd1 object
OnMsgArrived objOnMsgArrived1=new OnMsgArrived(mcd1.dispMyDelegate1);
// Create a delegate to point to dispMyDelegate2 of mcd2 object
OnMsgArrived objOnMsgArrived2=new OnMsgArrived(mcd2.dispMyDelegate2);
OnMsgArrived omc;
// Combine the two created delegates.Now omc would point to the head of a
// linked list of delegates
omc=(OnMsgArrived)Delegate.Combine(objOnMsgArrived1,objOnMsgArrived2);
Delegate [] arrayObjOnMsgArrived;
// Obtain the array of delegate references by invoking GetInvocationList()
arrayObjOnMsgArrived = omc.GetInvocationList();
OnMsgArrived objOnMsgArrived3;
// Now navigate through the array and call each delegate which in turn would
// call each of the methods
for(int i=0; i<arrayObjOnMsgArrived.Length; i++)
{
// Now call each of the delegates
objOnMsgArrived3 = (OnMsgArrived)arrayObjOnMsgArrived[i];
objOnMsgArrived3("string");
}
}
}