
Pro CSharp And The .NET 2.0 Platform (2005) [eng]
.pdf
264 C H A P T E R 8 ■ C A L L B A C K I N T E R FA C E S, D E L E G AT E S, A N D E V E N T S
// Invoke Add() method using delegate.
Console.WriteLine("10 + 10 is {0}", b(10, 10)); Console.ReadLine();
}
}
}
Again notice the format of the BinaryOp delegate, which can point to any method taking two integers and returning an integer. Given this, we have created a class named SimpleMath, which defines two static methods that (surprise, surprise) match the pattern defined by the BinaryOp delegate.
When you want to insert the target method to a given delegate, simply pass in the name of the method to the delegate’s constructor. At this point, you are able to invoke the member pointed to using a syntax that looks like a direct function invocation:
// Invoke() is really called here!
Console.WriteLine("10 + 10 is {0}", b(10, 10));
Under the hood, the runtime actually calls the compiler-generated Invoke() method. You can verify this fact for yourself if you open your assembly in ildasm.exe and investigate the CIL code within the Main() method:
.method private hidebysig static void Main(string[] args) cil managed
{
...
.locals init ([0] class SimpleDelegate.BinaryOp b) ldftn int32 SimpleDelegate.SimpleMath::Add(int32, int32)
...
newobj instance void SimpleDelegate.BinaryOp::.ctor(object, native int) stloc.0
ldstr "10 + 10 is {0}" ldloc.0
ldc.i4.s 10 ldc.i4.s 10
callvirt instance int32 SimpleDelegate.BinaryOp::Invoke(int32, int32)
...
}
Recall that .NET delegates (unlike C-style function pointers) are type safe. Therefore, if you attempt to pass a delegate a method that does not “match the pattern,” you receive a compile-time error. To illustrate, assume the SimpleMath class defines an additional method named SquareNumber():
public class SimpleMath
{
...
public static int SquareNumber(int a) { return a * a; }
}
Given that the BinaryOp delegate can only point to methods that take two integers and return an integer, the following code is illegal and will not compile:
// Error! Method does not match delegate pattern!
BinaryOp b = new BinaryOp(SimpleMath.SquareNumber);
Investigating a Delegate Object
Let’s spice up the current example by creating a helper function named DisplayDelegateInfo(). This method will print out names of the methods maintained by the incoming System.Delegate- derived type as well as the name of the class defining the method. To do so, we will iterate over the

C H A P T E R 8 ■ C A L L B A C K I N T E R FA C E S, D E L E G AT E S, A N D E V E N T S |
265 |
System.Delegate array returned by GetInvocationList(), invoking each object’s Target and Method properties:
static void DisplayDelegateInfo(Delegate delObj)
{
//Print the names of each member in the
//delegate's invocation list.
foreach (Delegate d in delObj.GetInvocationList())
{
Console.WriteLine("Method Name: {0}", d.Method); Console.WriteLine("Type Name: {0}", d.Target);
}
}
Assuming you have updated your Main() method to actually call this new helper method, you would find the output shown in Figure 8-3.
Figure 8-3. Examining a delegate’s invocation list
Notice that the name of the type (SimpleMath) is currently not displayed by the Target property. The reason has to do with the fact that our BinaryOp delegate is pointing to static methods and therefore there is no object to reference! However, if we update the Add() and Subtract methods to be nonstatic, we could create an instance of the SimpleMath type and specify the methods to invoke as follows:
static void Main(string[] args)
{
Console.WriteLine("***** Simple Delegate Example *****\n");
//.NET delegates can also point to instance methods.
SimpleMath m = new SimpleMath(); BinaryOp b = new BinaryOp(m.Add);
//Show information about this object.
DisplayDelegateInfo(b);
Console.WriteLine("\n10 + 10 is {0}", b(10, 10)); Console.ReadLine();
}
In this case, we would find the output shown in Figure 8-4.

266 C H A P T E R 8 ■ C A L L B A C K I N T E R FA C E S, D E L E G AT E S, A N D E V E N T S
Figure 8-4. Examining a delegate’s invocation list (once again)
■Source Code The SimpleDelegate project is located under the Chapter 8 subdirectory.
Retrofitting the Car Type with Delegates
Clearly, the previous SimpleDelegate example was intended to be purely illustrative in nature, given that there would be no reason to build a delegate simply to add two numbers. Hopefully, however, this example demystifies the process of working with delegate types. To provide a more realistic use of delegate types, let’s retrofit our Car type to send the Exploded and AboutToBlow notifications using
.NET delegates rather than a custom callback interface. Beyond no longer implementing IEngineEvents, here are the steps we will need to take:
•Define the AboutToBlow and Exploded delegates.
•Declare member variables of each delegate type in the Car class.
•Create helper functions on the Car that allow the caller to specify the methods maintained by the delegate member variables.
•Update the Accelerate() method to invoke the delegate’s invocation list under the correct circumstances.
Ponder the following updated Car class, which addresses the first three points:
public class Car
{
// Define the delegate types.
public delegate void AboutToBlow(string msg); public delegate void Exploded (string msg);
//Define member variables of each delegate type. private AboutToBlow almostDeadList;
private Exploded explodedList;
//Add members to the invocation lists using helper methods. public void OnAboutToBlow(AboutToBlow clientMethod)
{ almostDeadList = clientMethod; }
public void OnExploded(Exploded clientMethod) { explodedList = clientMethod; }
...
}
Notice in this example that we define the delegate types directly within the scope of the Car type. As you explore the base class libraries, you will find it is quite common to define a delegate

C H A P T E R 8 ■ C A L L B A C K I N T E R FA C E S, D E L E G AT E S, A N D E V E N T S |
267 |
within the scope of the type it naturally works with. On a related note, given that the compiler transforms a delegate into a full class definition, what we have actually done is create two nested classes.
Next, note that we declare two member variables (one for each delegate type) and two helper functions (OnAboutToBlow() and OnExploded()) that allow the client to add a method to the delegates invocation list. In concept, these methods are similar to the Advise() and Unadvise() method we created during the EventInterface example. Of course, in this case, the incoming parameter is a client-allocated delegate object rather than a class implementing a specific interface.
At this point, we need to update the Accelerate() method to invoke each delegate, rather than iterate over an ArrayList of client-side sinks (as we did in the EventInterface example). Here is the update:
public void Accelerate(int delta)
{
// If the car is dead, fire Exploded event. if (carIsDead)
{
if (explodedList != null)
explodedList("Sorry, this car is dead...");
}
else
{
currSpeed += delta;
// Almost dead?
if (10 == maxSpeed - currSpeed && almostDeadList != null)
{
almostDeadList("Careful buddy! Gonna blow!");
}
// Still OK!
if (currSpeed >= maxSpeed) carIsDead = true;
else
Console.WriteLine("->CurrSpeed = {0}", currSpeed);
}
}
Notice that before we invoke the methods maintained by the almostDeadList and explodedList member variables, we are checking them against a null value. The reason is that it will be the job of the caller to allocate these objects by calling the OnAboutToBlow() and OnExploded() helper methods. If the caller does not call these methods, and we attempt to invoke the delegate’s invocation list, we will trigger a NullReferenceException and bomb at runtime (which would obviously be a bad thing!).
Now that we have the delegate infrastructure in place, observe the updates to the Program class:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("***** Delegates as event enablers *****");
// Make a car as usual.
Car c1 = new Car("SlugBug", 100, 10);
// Register event handlers with Car type. c1.OnAboutToBlow(new Car.AboutToBlow(CarAboutToBlow)); c1.OnExploded(new Car.Exploded(CarExploded));

268 C H A P T E R 8 ■ C A L L B A C K I N T E R FA C E S, D E L E G AT E S, A N D E V E N T S
// Speed up (this will trigger the events).
Console.WriteLine("\n***** Speeding up *****");
for (int i = 0; i < 6; i++) c1.Accelerate(20);
Console.ReadLine();
}
// The Car will call these methods.
public static void CarAboutToBlow(string msg) { Console.WriteLine(msg); }
public static void CarExploded(string msg) { Console.WriteLine(msg); }
}
The only major point to be made here is the fact that the caller is the entity that assigns the delegate member variables via the helper registration methods. Also, because the AboutToBlow and Exploded delegates are nested within the Car class, we must allocate them using their full name (e.g., Car.AboutToBlow). Like any delegate constructor, we pass in the name of the method to add to the invocation list, which in this case are two static members on the Program class (if you wanted to wrap these methods in a new class, it would look very similar to the CarEventSink type of the
EventInterface example).
Enabling Multicasting
Recall that .NET delegates have the intrinsic ability to multicast. In other words, a delegate object can maintain a list of methods to call, rather than a single method. When you wish to add multiple methods to a delegate object, you simply make use of the overloaded += operator, rather than a direct assignment. To enable multicasting on the Car type, we could update the OnAboutToBlow() and OnExploded() methods as follows:
public class Car
{
// Add member to the invocation lists.
public void OnAboutToBlow(AboutToBlow clientMethod) { almostDeadList += clientMethod; }
public void OnExploded(Exploded clientMethod) { explodedList += clientMethod; }
...
}
With this, the caller can now register multiple targets:
class Program
{
static void Main(string[] args)
{
Car c1 = new Car("SlugBug", 100, 10);
// Register multiple event handlers! c1.OnAboutToBlow(new Car.AboutToBlow(CarAboutToBlow)); c1.OnAboutToBlow(new Car.AboutToBlow(CarIsAlmostDoomed)); c1.OnExploded(new Car.Exploded(CarExploded));
...
}

C H A P T E R 8 ■ C A L L B A C K I N T E R FA C E S, D E L E G AT E S, A N D E V E N T S |
269 |
// Car will call these.
public static void CarAboutToBlow(string msg) { Console.WriteLine(msg); }
public static void CarIsAlmostDoomed(string msg)
{Console.WriteLine("Critical Message from Car: {0}", msg); } public static void CarExploded(string msg)
{Console.WriteLine(msg); }
}
In terms of CIL code, the += operator resolves to a call to the static Delegate.Combine() method (you could call Delegate.Combine() directly, but the += operator offers a simpler alternative). Ponder the following CIL implementation of OnAboutToBlow():
.method public hidebysig instance void OnAboutToBlow
(class CarDelegate.Car/AboutToBlow clientMethod) cil managed
{
.maxstack 8 ldarg.0
dup
ldfld class CarDelegate.Car/AboutToBlow CarDelegate.Car::almostDeadList ldarg.1
call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(
class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
castclass CarDelegate.Car/AboutToBlow
stfld class CarDelegate.Car/AboutToBlow CarDelegate.Car::almostDeadList ret
}
The Delegate class also defines a static Remove() method that allows a caller to dynamically remove a member from the invocation list. As you may be suspecting, C# developers can leverage the overloaded -= operator as a shorthand notation. If you wish to allow the caller the option to detach from the AboutToBlow and Exploded notifications, you could add the following additional helper methods to the Car type (note the -= operators at work):
public class Car
{
// Remove member from the invocation lists.
public void RemoveAboutToBlow(AboutToBlow clientMethod) { almostDeadList -= clientMethod; }
public void RemoveExploded(Exploded clientMethod) { explodedList -= clientMethod; }
...
}
Again, the -= syntax is simply a shorthand notation for manually calling the static Delegate.Remove() method, as illustrated by the following CIL code for the RemoveAboutToBlow() member of the Car type:
.method public hidebysig instance void RemoveAboutToBlow(class CarDelegate.Car/AboutToBlow clientMethod) cil managed
{
.maxstack 8 ldarg.0
dup
ldfld class CarDelegate.Car/AboutToBlow CarDelegate.Car::almostDeadList ldarg.1
call class [mscorlib]System.Delegate

270 C H A P T E R 8 ■ C A L L B A C K I N T E R FA C E S, D E L E G AT E S, A N D E V E N T S
[mscorlib]System.Delegate::Remove( class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
castclass CarDelegate.Car/AboutToBlow
stfld class CarDelegate.Car/AboutToBlow CarDelegate.Car::almostDeadList ret
}
If the caller does indeed wish to remove an item from a delegate’s invocation list, you will need to supply the same delegate object you added previously. Thus, we could stop receiving the Exploded notification by updating Main() as follows:
static void Main(string[] args)
{
Car c1 = new Car("SlugBug", 100, 10);
// Hold onto Car.Exploded delegate object for later use.
Car.Exploded d = new Car.Exploded(CarExploded); c1.OnExploded(d);
...
//Remove CarExploded method
//from invocation list. c1.RemoveExploded(d);
...
}
The output of our CarDelegate application can be seen in Figure 8-5.
Figure 8-5. The CarDelegate application at work
■Source Code The CarDelegate project is located under the Chapter 8 subdirectory.
A More Elaborate Delegate Example
To illustrate a more advanced use of delegates, let’s begin by updating the Car class to include two new Boolean member variables. The first is used to determine whether your automobile is due for a wash (isDirty); the other represents whether 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:

C H A P T E R 8 ■ C A L L B A C K I N T E R FA C E S, D E L E G AT E S, A N D E V E N T S |
271 |
// Updated Car class. public class Car
{
...
//Are we in need of a wash? Need to rotate tires? private bool isDirty;
private bool shouldRotate;
//Extra params to set bools.
public Car(string name, int max, int curr, bool washCar, bool rotateTires)
{
...
isDirty = washCar; shouldRotate = rotateTires;
}
public bool Dirty
{
get{ return isDirty; } set{ isDirty = value; }
}
public bool Rotate
{
get{ return shouldRotate; } set{ shouldRotate = value; }
}
}
Now, also assume the Car type nests a new delegate, CarDelegate:
// Car defines yet another delegate. public class Car
{
...
//Can call any method taking a Car as
//a parameter and returning nothing. 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.
Delegates As Parameters
Now that you have a new delegate type that points to methods taking a Car parameter and returning nothing, you can create other functions that take this delegate as a parameter. To illustrate, assume you have a new class named Garage. This type maintains a collection of Car types contained in a System.Collections.ArrayList. Upon creation, the ArrayList is filled with some initial Car types:
// The Garage class maintains a list of Car types.
Using System.Collections;
...
public class Garage
{
// A list of all cars in the garage.
ArrayList theCars = new ArrayList();

272 C H A P T E R 8 ■ C A L L B A C K I N T E R FA C E S, D E L E G AT E S, A N D E V E N T S
// Create the cars in the garage. public Garage()
{
// Recall, we updated the ctor to set isDirty and shouldRotate. theCars.Add(new Car("Viper", 100, 0, true, false)); theCars.Add(new Car("Fred", 100, 0, false, false)); theCars.Add(new Car("BillyBob", 100, 0, false, true));
}
}
More importantly, the Garage class defines a public ProcessCars() method, which takes a single argument of our new delegate type (Car.CarDelegate). In the implementation of ProcessCars(), you pass each Car in your collection as a parameter to the “function pointed to” by the delegate.
ProcessCars() also makes use of the Target and Method members of System.MulticastDelegate to determine exactly which function the delegate is currently pointing to:
// The Garage class has a method that makes use of the CarDelegate.
Using System.Collections;
...
public class Garage
{
...
// This method takes a Car.CarDelegate as a parameter. public void ProcessCars(Car.CarDelegate proc)
{
//Where are we forwarding the call?
Console.WriteLine("***** Calling: {0} *****",
d.Method);
//Are we calling an instance method or a static method? if(proc.Target != null)
Console.WriteLine("—>Target: {0} ", proc.Target);
else
Console.WriteLine("—>Target is a static method");
//Call the method "pointed to," passing in each car. foreach (Car c in theCars)
{
Console.WriteLine("\n-> Processing a Car"); proc(c);
}
}
}
Like any delegate operation, when calling ProcessCars(), we send in the name of the method that should handle this request. Recall that these methods may be either static or instance level. For the sake of argument, assume these are instance members named WashCar() and RotateTires() that are defined by a new class named ServiceDepartment. Notice that these two methods are making use of the new Rotate and Dirty properties of the Car type.
//This class defines method to be invoked by
//the Car.CarDelegate type.
public class ServiceDepartment
{
public void WashCar(Car c)
{

C H A P T E R 8 ■ C A L L B A C K I N T E R FA C E S, D E L E G AT E S, A N D E V E N T S |
273 |
if(c.Dirty)
Console.WriteLine("Cleaning a car");
else
Console.WriteLine("This car is already clean...");
}
public void RotateTires(Car c)
{
if(c.Rotate)
Console.WriteLine("Tires have been rotated");
else
Console.WriteLine("Don't need to be rotated...");
}
}
Now, to illustrate the interplay between the new Car.CarDelegate, Garage, and ServiceDepartment types, consider the following usage:
//The Garage delegates all work orders to the ServiceDepartment
//(finding a good mechanic is always a problem...)
public class Program
{
static void Main(string[] args)
{
//Make the garage.
Garage g = new Garage();
//Make the service department.
ServiceDepartment sd = new ServiceDepartment();
//The Garage washes cars and rotates tires
//by delegating to the ServiceDepartment. g.ProcessCars(new Car.CarDelegate(sd.WashCar)); g.ProcessCars(new Car.CarDelegate(sd.RotateTires)); Console.ReadLine();
}
}
Figure 8-6 shows the current output.