Introduction
When you declare a delegate, you are actually defining a class. When you declare an event, you are actually declaring a private delegate instance and a property which exposes this instance through an add and remove accessor. Well, you aren’t doing all this – it’s the compiler that does all this for you. Let’s see how.
Delegates
The custom delegate class
Let us define a simple delegate and compile it:
delegate int MyDelegate(string someValue);
For the above statement, the compiler would generate the following custom delegate class:
internal delegate int MyDelegate(string someValue)
{
public MyDelegate(object object, IntPtr method);
public virtual IAsyncResult BeginInvoke(string someValue,
AsyncCallback callback, object object);
public virtual int EndInvoke(IAsyncResult result);
public override int Invoke(string someValue);
}
Internally, this custom delegate class inherits from System.MulticastDelegate
, which in turn inherits from System.Delegate
, both of which are abstract. System.Delegate
implements the functionality of invoking a single method target. System.MulticastDelegate
acts like a wrapper over System.Delegate
to provide the functionality of invoking a chain of method targets.
The point is, delegates are internal classes and these classes inherit from System.MulticastDelegate which makes them capable of invoking multiple method targets.
Also note that if a delegate is declared inside a class, the custom delegate class would be defined within the enclosing class. In other words, the custom delegate class would be a class within another class.
Delegate constructors
As displayed in the code, the custom delegate class constructor accepts two parameters – an object
and an IntPtr
. The first argument represents the target object instance upon which the required method should be invoked. The second argument holds a pointer to the method that is to be invoked.
Constructors of System.MulticastDelegate
and System.Delegate
are similar to this custom delegate class constructor except that the second parameter for the base classes is a string
. Though the code for the custom delegate class constructor is hidden, it should be safe to assume that it would figure out the method name from the IntPtr
and pass it on to the base class constructors.
Within the System.Delegate
constructor, a private
class field of type RuntimeMethodInfo
gets initialized on the basis of the value passed from the custom delegate constructor. RuntimeMethodInfo
is an internal type and it inherits from System.Reflection.MethodInfo
– in other words, delegates use reflection. System.Delegate
would use this private
field to find out which method is to be invoked.
System.Delegate
also defines a public static CreateDelegate
method which creates and returns a delegate type. Both this method and the constructor use the private
InternalCreate
method which handles the actual delegate type creation process.
As mentioned before, System.MulticastDelegate
provides the functionality to invoke a chain of method targets. To accomplish this, System.MulticastDelegate
defines a private
System.MulticastDelegate
field, which would represent any previous delegate instance (a pattern similar to a linked list). When a delegate or an event is invoked, each instance in this list would be invoked in the sequence they were registered.
Also note that the virtual
System.Delegate.GetInvocationList
method is overridden in System.MulticastDelegate
to return a System.Delegate
array in the sequence the targets were registered.
Invoke methods
In the custom delegate class, BeginInvoke
and Invoke
methods are generated with respect to the delegate signature defined. This is how the delegates ensure type safety.
Also note that though the Invoke
method is marked as override
, there is no Invoke
method declared in either of the base classes. It could be safe to assume that the compiler generates such a method at build time which the custom class overrides.
Delegate registration
Let us now expand on the existing code and add a delegate class field and register a method target as follows:
class Class1
{
[STAThread]
static void Main(string[] args)
{
MyClass obj = new MyClass();
obj.myDelegateInstance = new MyDelegate(Class1.Target);
}
static int Target(string someValue)
{
return 0;
}
}
delegate int MyDelegate(string someValue);
class MyClass
{
public MyDelegate myDelegateInstance;
}
The statement marked in bold uses the = operator to attach a new delegate target. The compiler would generate no custom code for the = operator. However, if we were to use the += operator like the statement below, the results would be different:
class1.myDelegateInstance += new MyDelegate(Class1.Target);
For the above statement, the compiler would produce the following code:
class1.myDelegateInstance =
((MyDelegate) Delegate.Combine(class1.myDelegateInstance,
new MyDelegate(Class1.Target)));
The reason for this change is that since we are using the += operator, the compiler needs to attach a new delegate target to an existing chain of delegate targets. The statement generated above assists in registering multiple target methods, by calling the Delegate.Combine
method. Delegate.Combine
invokes Multicast.Combine
which initializes the private
System.Delegate
class field we talked about before (the one which holds the previous delegate instance). A chain of delegate targets is thus created.
Events
Events are just properties (or at least property-like constructs) which the complier generates when you declare an event. Each event is a wrapping property over an existing private
delegate instance field. This property exposes only add and remove accessors, which prevents operations like = on an event.
Let us modify our class to declare an event:
delegate int MyDelegate(string someValue);
class MyClass
{
public event MyDelegate myEventInstance;
}
The compiler generated code for the above code will be as follows:
internal class MyClass
{
public event MyDelegate myEventInstance { add; remove; };
private MyDelegate myEventInstance;
public MyClass();
}
Note the addition of the public
myEventInstance
property-like construct over the private
delegate instance myEventInstance
. When you register and deregister handlers on events, it is the public
custom event property you would access. Since the actual delegate instance myEventInstance
is private
, the only operations exposed are those that are accessible through the public
myEventInstance
custom event property – the add and remove operations. This is the reason why events support only += and -= operators.
The following is how the add and remove handlers of the custom event property will look like:
public void add_myEventInstance(MyDelegate value)
{
this.myEventInstance =
((MyDelegate) Delegate.Combine(this.myEventInstance, value));
}
public void remove_myEventInstance(MyDelegate value)
{
this.myEventInstance =
((MyDelegate) Delegate.Remove(this.myEventInstance, value));
}
The code is exactly the same as with adding and removing targets with delegates, which we saw earlier. You could also see that the add and remove handlers are just like the get and set accessors generated for properties.
The point is, events are just properties over private delegate instances which allow only add and remove operations.
Conclusion
This brings us to the end of this article. Hope it was an enjoyable ride. If someone could shed light on the hidden code generated by the compiler and the assumptions I have taken, please let me know – I would be most grateful and would update the content; thanks!
Though this topic may not be of much use in most scenarios, internals are usually interesting. You might also want to try creating your own delegate classes using Reflection, just for fun!