Introduction
When we declare a delegate; under the hood the compiler defines a class. When we declare an event, the compiler defines a private delegate instance and a property which encapsulates this instance through a set of add and remove accessors.
Delegates: the custom delegate class
Let us define a custom delegate and compile it:
public delegate int Processor(string str);
Under the hood; the compiler generates a custom delegate class which inherits from System.MulticastDelegate which, again, inherits from System.Delegate, both of which are abstract. System.Delegate implements the functionality of invoking a single method target. System.MulticastDelegate acts as a wrapper over System.Delegate to provide the functionality of invoking a chain of method targets. Three additional methods are defined by the compiler for a custom delegate class: BeginInvoke, EndInvoke and Invoke. If you do an ‘ildasm’ on your compiled project you can see these methods definitions as follows:
.method public hidebysig newslot virtual instance class [mscorlib]System.IAsyncResult BeginInvoke(string str, class [mscorlib]System.AsyncCallback callback, object 'object') runtime managed { } // end of method Processor::BeginInvoke
.method public hidebysig newslot virtual instance int32 EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed { } // end of method Processor::EndInvoke
.method public hidebysig newslot virtual instance int32 Invoke(string str) runtime managed { } // end of method Processor::Invoke
Constructor of a delegate:
The custom delegate class defines a parameterized constructor which accepts two parameters: an object and an IntPtr. The first argument identifies the target object instance against which the target method should be invoked. The second argument holds a pointer to the target method. Here is the MSIL code for the constructor pertaining to the delegate defined above:
.method public hidebysig specialname rtspecialname instance void .ctor(object 'object', native int 'method') runtime managed { } // end of method Processor::.ctor
The fact that the constructor takes an object instance just reiterates the object oriented aspect of delegates in C#: they are not mere function pointers but also have the capability of attached them to the instance against which the target method is to be invoked. Delegates can be used to call static methods as well, in which case the object parameter would be null.
Invocation of methods:
In the custom delegate class, BeginInvoke and Invoke methods are defined maintaining the delegate signature (single parameter string here). This is how delegates ensure type safety. Now let us define a class which would use Processor delegate:
namespace ClassLibrary { public class DelegatesEventsDemo { public void CallDelegates(string str) { Processor pd = new Processor(Parse); //Call the delegate pd(str); }
private int Parse(string str) { return Int32.Parse(str); } } }
If you look at the MSIL code for the CallDelegates method; you would see that the delegate invocation is translated to a call to the Invoke method:
callvirt instance int32 ClassLibrary.Processor::Invoke(string)
BeginInvoke and EndInvoke are used in asynchronous usage of delegates; which we won’t be discussing here.
Registration of delegates:
One of the capabilities of the delegates is to allow registration of multiple target methods. Let us define another method in our DelegatesEventsDemo class:
private int FindLength(string str) { return str.Length; }
And have it registered in the CallDelegates method:
pd += new Processor(FindLength);
What happens behind the scene for this registration? Well; a call to the method Delegate.Combine is made; as revealed by the MSIL code below:
call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
It is to be noted that there is an additional field in a MulticastDelegate called prev which refers to another MulticastDelegate. What method Delegate.Combine does is that it combines the two delegates and makes second’s prev field point to first. It then returns the head of the linked list. Thus, when a delegate is added to a multicast delegate, the MulticastDelegate class creates a new instance of the delegate type, stores the object reference and the pointer for the target method into the new instance, and adds the new delegate instance as the next item in a list of delegate instances. In effect, the MulticastDelegate class maintains a linked list of delegate objects. A chain of delegate target is thus created.
Events
Events are property like constructs which the compiler generates when an event is declared. An event is basically a wrapping property over an existing private delegate instance field. The property exposes only add and remove accessors (that’s why you don’t see = operation available on an event). Let us declare an event in our DelegatesEventsDemo class:
public event Processor ProcessorEvent;
This would end up in having a MSIL code like this at the DelegatesEventsDemo class level:
.field private class ClassLibrary.Processor ProcessorEvent
.event ClassLibrary.Processor ProcessorEvent { .addon instance void ClassLibrary.DelegatesEventsDemo::add_ProcessorEvent(class ClassLibrary.Processor) .removeon instance void ClassLibrary.DelegatesEventsDemo::remove_ProcessorEvent(class ClassLibrary.Processor) } // end of event DelegatesEventsDemo::ProcessorEvent
So we are having a private field of the associated delegate type and a pair of add/ remove property constructs. This aids in registering/ removing “event handlers” against that event. If we dig further into the MSIL code; we can find MSIL code for the add and remove handlers is basically having call to Delegate.Combine and Delegate.Remove respectively. For brevity I have omitted remove_ProcessorEvent code, but it will be very similarly with the call to Combine being replaced by a call to Remove.
.method public hidebysig specialname instance void add_ProcessorEvent(class ClassLibrary.Processor 'value') cil managed synchronized { // Code size 24 (0x18) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldarg.0 IL_0002: ldfld class ClassLibrary.Processor ClassLibrary.DelegatesEventsDemo::ProcessorEvent IL_0007: ldarg.1 IL_0008: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate) IL_000d: castclass ClassLibrary.Processor IL_0012: stfld class ClassLibrary.Processor ClassLibrary.DelegatesEventsDemo::ProcessorEvent IL_0017: ret } // end of method DelegatesEventsDemo::add_ProcessorEvent
Summary The fundamental aspect here is that delegates are basically classes and these classes inherit from System.MulticastDelegate; which provides them the capability of invoking multiple method targets and events are just properties over private delegate instances which allow only add and remove operations.
|
| Author: chaithanya 14 Jul 2008 | Member Level: Silver Points : 1 |
Hi,
Its a good article about Delegate and Events, as i am new to this concepts, this resource helped me to learn the usage of Delegates and Events.
|