Understanding (and Using) DelegatesUp until this point, every sample application you have developed added various bits of code to Main(), which (in some way or another) sent messages to a given object. However, you have not yet examined how these objects can talk back to the object that created them in the first place. In the "real world" it is quite common for the objects in a system to engage in a two-way conversation. Thus, let's examine a number of ways in which objects can be programmed to do this very thing.As you may know, the Windows API makes frequent use of function pointers to create entities termed "callback functions" or simply "callbacks." Using callbacks, programmers are able to configure one function to report back to (call back) another function in the application. The problem with standard C(++)callback functions is that they represent nothing more than a simple memory address. Ideally, C(++) callbacks could be configured to include additional type-safe information such as the number of (and types of) parameters, return value, and calling convention. Sadly, this is not the case in traditional C(++)/Win32 callback functions.In C#, the callback technique is accomplished in a much safer and more objectoriented manner using the "delegate" keyword. When you wish to create a delegate in C#, you not only specify the name of the method, but the set of parameters (if any) and return type as well. Under the hood, the "delegate" keyword represents an instance of a class deriving from System.MulticastDelegate. Thus, when you write: public delegate void PlayAcidHouse(object PaulOakenfold, int volume); the C# compiler produces a new class, which looks something like the following:public class PlayAcidHouse : System System.MulticastDelegate{PlayAcidHouse(object target, int ptr);// The synchronous Invoke() method.public void virtual Invoke(object PaulOakenfold, int volume);// You also receive an asynchronous version of the same callback.public virtual IAsyncResult BeginInvoke(object PaulOakenfold, int volume,AsyncCallback cb, object o);public virtual void EndInvoke(IAsyncResult result);}Notice that the class that is created on your behalf contains two public methods that enable you to synchronously or asynchronously work with the delegate (Invoke() and BeginInvoke() respectively). To keep things simple, I will focus only on the synchronous behavior of the MulticastDelegate type.Building an Example DelegateTo illustrate the use of delegates, let's begin by updating the Car class to include two new Boolean member variables. The first is used to determine if your automobile is due for a wash (isDirty); the other represents if the car in question is in need of a tire rotation (shouldRotate). To enable the object user to interact with this new state data, Car also defines some additional properties and an updated constructor. Here is the story so far:
// Another updated Car class.
public class Car{// NEW! Are we in need of a wash? Need to rotate tires?private bool isDirty;private bool shouldRotate;// Extra para params to set bools.public Car(string name, int max, int curr, bool dirty, bool rotate rotate){
isDirty = dirty; shouldRotate = rotate;}public bool Dirty // Get and set isDirty.{get{ return isDirty; }set{ isDirty = value; }}public bool Rotate // Get and set shouldRotate.{get{ return shouldRotate; }set{ shouldRotate = value; }}}Now, assume you have declared the following delegate (which again, is nothing more than an object-oriented wrapper around a function pointer) within your current namespace:// This delegate is actually a class encapsulating a function pointer// to 'some method' taking a Car as a parameter and returning void.public delegate void CarDelegate(Car c);Here, you have created a delegate named CarDelegate. The CarDelegate type represents "some" function taking a Car as a parameter and returning void. If you were to examine the internal representation of this type using ILDasm.exe, you would see something like Figure 5-3 (notice the "extends" informational node).Figure 5-3. C# delegates represent a class deriving from MulticastDelegate.Delegates as Nested TypesCurrently, your delegate is decoupled from its logically related Car type (given that you have simply declared the CarDelegate type within the defining namespace). While there is nothing horribly wrong with the approach, a more enlightened alternative would be to define the CarDelegate directly within the Car class:// This time, define the delegate as part of the class definition.public class Car : Object{ // This is represented as Car$CarDelegate (i.e., a nested type). public delegate void CarDelegate(Car c);
}Given that the "delegate" keyword produces a new class deriving from System.MulticastDelegate, the CarDelegate is in fact a nested type definition! If youcheck ILDasm.exe (see Figure 5-4), you will see the truth of the matter.Figure 5-4. Nesting the delegateMembers of System.MutlicastDelegateSo to review thus far, when you create delegates, you indirectly build a type that derives from System.MulticastDelegate (which by the way, derives from the System.Delegate base class). Table 5-2 illustrates some interesting inherited members to be aware of.Table 5-2. Select Inherited Members
InheritedMember
Meaning in Life
Method
This property returns the name of the method pointed to.
Target
If the method pointed to is a member of a class, this member returns the name of the class. If the value returned from Target equals null, the method pointed to is static.
Combine()
This static method is used to build a delegate that points to a number of different functions.
GetInvocationList()
Returns an array of Delegate types, each representing an entry in the list of function pointers.
Remove()
This static method removes a delegate from the list of function pointers.
Multicast delegates are capable of pointing to any number of functions, because this class has the capability to hold multiple function pointers using an internal linked list. The function pointers themselves can be added to the linked list using the Combine() method or the overloaded + operator. To remove a function from the internal list, call Remove().