
Pro CSharp 2008 And The .NET 3.5 Platform [eng]
.pdf
362 CHAPTER 11 ■ DELEGATES, EVENTS, AND LAMBDAS
■Source Code The GenericDelegate project is located under the Chapter 11 directory.
That wraps up our initial look at the .NET delegate type. We will revisit some additional details of working with delegates at the conclusion of this chapter and once again in Chapter 18 during our examination of multithreading. Until then, let’s move on to the related topic of the C# event keyword.
Understanding C# Events
Delegates are fairly interesting constructs in that they enable objects in memory to engage in a twoway conversation. As you may agree, however, working with delegates in the raw can entail some boilerplate code (defining the delegate, declaring necessary member variables, and creating custom registration/unregistration methods to preserve encapsulation, etc.).
Typing time aside, another issue with using delegates in the raw as your application’s callback mechanism is the fact that if you do not define a class’s delegate member variables as private, the caller will have direct access to the delegate objects. If this were the case, the caller would be able to reassign the variable to a new delegate object (effectively deleting the current list of functions to call) and worse yet, the caller would be able to directly invoke the delegate’s invocation list. To illustrate this problem, consider the following reworking (and simplification) of the previous CarDelegate example:
public class Car
{
// A single delegate
public delegate void Exploded(string msg);
//Now public! No more helper functions! public Exploded explodedList;
//Just fire out the Exploded notification. public void Accelerate(int delta)
{
if (explodedList != null) explodedList("Sorry, this car is dead...");
}
}
Notice that we no longer have private delegate member variables encapsulated with custom registration methods. Because these members are indeed public, the caller can directly access the explodedList member and resign this type to new Exploded objects and invoke the delegate whenever it so chooses:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("***** Agh! No Encapsulation! *****\n");
// Make a Car.
Car myCar = new Car();

CHAPTER 11 ■ DELEGATES, EVENTS, AND LAMBDAS |
363 |
//We have direct access to the delegate! myCar.explodedList = new Car.Exploded(CallWhenExploded); myCar.Accelerate(10);
//We can now assign to a whole new object...
//confusing at best.
myCar.explodedList = new Car.Exploded(CallHereToo); myCar.Accelerate(10);
// The caller can also directly invoke the delegate! myCar.explodedList.Invoke("hee, hee, hee...");
}
static void CallWhenExploded(string msg) { Console.WriteLine(msg); }
static void CallHereToo(string msg) { Console.WriteLine(msg); }
}
Exposing public delegate members breaks encapsulation, which not only can lead to code that is hard to maintain (and debug), but could also open your application to possible security risks! Obviously, you would not want to give other applications the power to change what a delegate is pointing to or the power to invoke the members without your permission.
■Source Code The PublicDelegateProblem project is located under the Chapter 11 subdirectory.
The Event Keyword
As a shortcut to having to build custom methods to add or remove methods to a delegate’s invocation list, C# provides the event keyword. When the compiler processes the event keyword, you are automatically provided with registration and unregistration methods as well as any necessary member variables for your delegate types. These delegate member variables are always declared private, and therefore they are not directly exposed from the object firing the event. To be sure, the event keyword is little more than syntactic sugar in that it simply saves you some typing time.
Defining an event is a two-step process. First, you need to define a delegate that will hold the list of methods to be called when the event is fired. Next, you declare an event (using the C# event keyword) in terms of the related delegate.
To illustrate the event keyword, this iteration of the Car type will define two events (named identically to the previous AboutToBlow and Exploded delegates). These events are associated to a single delegate type named CarEventHandler. Here are the initial updates to the Car type:
public class Car
{
//This delegate works in conjunction with the
//Car's events.
public delegate void CarEventHandler(string msg);
// This car can send these events. public event CarEventHandler Exploded; public event CarEventHandler AboutToBlow;
...
}

364 CHAPTER 11 ■ DELEGATES, EVENTS, AND LAMBDAS
Sending an event to the caller is as simple as specifying the event by name as well as any required parameters as defined by the associated delegate. To ensure that the caller has indeed registered with the event, you will want to check the event against a null value before invoking the delegate’s method set. These things being said, here is the new iteration of the Car’s Accelerate() method:
public void Accelerate(int delta)
{
// If the car is dead, fire Exploded event. if (carIsDead)
{
if (Exploded != null)
Exploded("Sorry, this car is dead...");
}
else
{
currSpeed += delta;
// Almost dead?
if (10 == maxSpeed - currSpeed && AboutToBlow != null)
{
AboutToBlow("Careful buddy! Gonna blow!");
}
// Still OK!
if (currSpeed >= maxSpeed) carIsDead = true;
else
Console.WriteLine("->CurrSpeed = {0}", currSpeed);
}
}
With this, you have configured the car to send two custom events without the need to define custom registration functions or declare delegate member variables. You will see the usage of this new automobile in just a moment, but first, let’s check the event architecture in a bit more detail.
Events Under the Hood
A C# event actually expands into two hidden public methods, one having an add_ prefix, the other having a remove_ prefix. This prefix is followed by the name of the C# event. For example, the Exploded event results in two CIL methods named add_Exploded() and remove_Exploded(). In addition to the add_XXX() and remove_XXX() methods, the CIL-level event definition associates the correct delegate to a given event.
If you were to check out the CIL instructions behind add_AboutToBlow(), you would find code that looks just about identical to the OnAboutToBlow() helper method you wrote previously in the
CarDelegate example (note the call to Delegate.Combine()):
.method public hidebysig specialname instance void add_AboutToBlow(class CarEvents.Car/CarEventHandler 'value') cil managed synchronized
{
.maxstack 8 ldarg.0 ldarg.0

CHAPTER 11 ■ DELEGATES, EVENTS, AND LAMBDAS |
365 |
ldfld class CarEvents.Car/CarEventHandler CarEvents.Car::AboutToBlow ldarg.1
call class [mscorlib]System.Delegate
[mscorlib]System.Delegate::Combine(
class [mscorlib]System.Delegate, class [mscorlib]System.Delegate) castclass CarEvents.Car/CarEventHandler
stfld class CarEvents.Car/CarEventHandler CarEvents.Car::AboutToBlow ret
}
As you would expect, remove_AboutToBlow() will indirectly call Delegate.Remove() and is more or less identical to the previous RemoveAboutToBlow() helper method:
.method public hidebysig specialname instance void remove_AboutToBlow(class CarEvents.Car/CarEventHandler 'value') cil managed synchronized
{
.maxstack 8 ldarg.0 ldarg.0
ldfld class CarEvents.Car/CarEventHandler CarEvents.Car::AboutToBlow ldarg.1
call class [mscorlib]System.Delegate
[mscorlib]System.Delegate::Remove(
class [mscorlib]System.Delegate, class [mscorlib]System.Delegate) castclass CarEvents.Car/CarEventHandler
stfld class CarEvents.Car/CarEventHandler CarEvents.Car::AboutToBlow ret
}
Finally, the CIL code representing the event itself makes use of the .addon and .removeon directives to map the names of the correct add_XXX() and remove_XXX() methods to invoke:
.event CarEvents.Car/EngineHandler AboutToBlow
{
.addon void CarEvents.Car::add_AboutToBlow (class CarEvents.Car/CarEngineHandler)
.removeon void CarEvents.Car::remove_AboutToBlow (class CarEvents.Car/CarEngineHandler)
}
Now that you understand how to build a class that can send C# events (and are aware that events are little more than a typing time-saver), the next big question is how to “listen to” the incoming events on the caller’s side.
Listening to Incoming Events
C# events also simplify the act of registering the caller-side event handlers. Rather than having to specify custom helper methods, the caller simply makes use of the += and -= operators directly (which triggers the correct add_XXX() or remove_XXX() method in the background). When you wish to register with an event, follow the pattern shown here:
//ObjectVariable.EventName +=
//new AssociatedDelegate(functionToCall);
Car.EngineHandler d = new Car.CarEventHandler(CarExplodedEventHandler) myCar.Exploded += d;

366 CHAPTER 11 ■ DELEGATES, EVENTS, AND LAMBDAS
When you wish to detach from a source of events, use the -= operator:
// ObjectVariable.EventName -= delegateObject; myCar.Exploded -= d;
Given these very predictable patterns, here is the refactored Main() method, now using the C# event registration syntax:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Events *****\n");
Car c1 = new Car("SlugBug", 100, 10);
// Register event handlers.
c1.AboutToBlow += new Car.CarEventHandler(CarIsAlmostDoomed); c1.AboutToBlow += new Car.CarEventHandler(CarAboutToBlow);
Car.CarEventHandler d = new Car.CarEventHandler(CarExploded); c1.Exploded += d;
Console.WriteLine("***** Speeding up *****");
for (int i = 0; i < 6; i++) c1.Accelerate(20);
//Remove CarExploded method
//from invocation list. c1.Exploded -= d;
Console.WriteLine("\n***** Speeding up *****");
for (int i = 0; i < 6; i++) c1.Accelerate(20);
Console.ReadLine();
}
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); }
}
Simplifying Event Registration Using Visual Studio 2008
Visual Studio 2008 offers assistance with the process of registering event handlers. When you apply the += syntax during the act of event registration, you will find an IntelliSense window is displayed inviting you to hit the Tab key to autofill the associated delegate instance (see Figure 11-6).

CHAPTER 11 ■ DELEGATES, EVENTS, AND LAMBDAS |
367 |
Figure 11-6. Delegate selection IntelliSense
Once you do hit the Tab key, you are then invited to enter the name of the event handler to be generated (or simply accept the default name) as shown in Figure 11-7.
Figure 11-7. Delegate target format IntelliSense
Once you hit the Tab key again, you will be provided with stub code in the correct format of the delegate target (note that this method has been declared static due to the fact that the event was registered within the static Main() method):
static void c1_AboutToBlow(string msg)
{
// Add your code!
}
This IntelliSense feature is available to all .NET events in the base class libraries. This IDE feature is a massive time-saver, given that this removes you from the act of needing to search the .NET help system to figure out the correct delegate to use with a particular event as well as the format of the delegate target.
■Source Code The CarEvents project is located under the Chapter 11 subdirectory.
A “Prim-and-Proper” Event
Truth be told, there is one final enhancement we could make to the current iteration of the Car type that mirrors Microsoft’s recommended event pattern. As you begin to explore the events sent by a given type in the base class libraries, you will find that the first parameter of the underlying delegate is a System.Object, while the second parameter is a type deriving from System.EventArgs.

368 CHAPTER 11 ■ DELEGATES, EVENTS, AND LAMBDAS
The System.Object argument represents a reference to the object that sent the event (such as the Car), while the second parameter represents information regarding the event at hand. The System.EventArgs base class represents an event that is not sending any custom information:
public class EventArgs
{
public static readonly System.EventArgs Empty; public EventArgs();
}
For simple events, you can pass an instance of EventArgs directly. However, when you wish to pass along custom data, you should build a suitable class deriving from EventArgs. For our example, assume we have a class named CarEventArgs, which maintains a string representing the message sent to the receiver:
public class CarEventArgs : EventArgs
{
public readonly string msg;
public CarEventArgs(string message)
{
msg = message;
}
}
With this, we would now update the CarEventHandler delegate as follows (the events would be unchanged):
public class Car
{
public delegate void CarEventHandler(object sender, CarEventArgs e);
...
}
Here, when firing our events from within the Accelerate() method, we would now need to supply a reference to the current Car (via the this keyword) and an instance of our CarEventArgs type. For example, consider the following update:
public void Accelerate(int delta)
{
// If the car is dead, fire Exploded event. if (carIsDead)
{
if (Exploded != null)
Exploded(this, new CarEventArgs("Sorry, this car is dead..."));
}
...
}
On the caller’s side, all we would need to do is update our event handlers to receive the incoming parameters and obtain the message via our read-only field. For example:
public static void CarAboutToBlow(object sender, CarEventArgs e)
{
Console.WriteLine("{0} says: {1}", sender, e.msg);
}
If the receiver wishes to interact with the object that sent the event, we can explicitly cast the System.Object. Thus, if we wish to power down the radio when the Car object is about to meet its maker, we could author an event handler looking something like the following:

CHAPTER 11 ■ DELEGATES, EVENTS, AND LAMBDAS |
369 |
public static void CarIsAlmostDoomed(object sender, CarEventArgs e)
{
//Just to be safe, perform a
//runtime check before casting. if (sender is Car)
{
Car c = (Car)sender; c.CrankTunes(false);
}
Console.WriteLine("Critical Message from {0}: {1}", sender, e.msg);
}
■Source Code The PrimAndProperCarEvents project is located under the Chapter 11 subdirectory.
The Generic EventHandler<T> Delegate
Given that so many custom delegates take an object as the first parameter and an EventArgs descendent as the second, you could further streamline the previous example by using the generic EventHandler<T> type, where T is your custom EventArgs type. Consider the following update to the Car type (notice how we no longer need to build a custom delegate type at all):
public class Car
{
public event EventHandler<CarEventArgs> Exploded; public event EventHandler<CarEventArgs> AboutToBlow;
...
}
The Main() method could then make use of EventHandler<CarEventArgs> anywhere we previously specified CarEventHandler:
static void Main(string[] args)
{
Console.WriteLine("***** Prim and Proper Events *****\n");
// Make a car as usual.
Car c1 = new Car("SlugBug", 100, 10);
// Register event handlers.
c1.AboutToBlow += new EventHandler<CarEventArgs>(CarIsAlmostDoomed); c1.AboutToBlow += new EventHandler<CarEventArgs>(CarAboutToBlow);
EventHandler<CarEventArgs> d = new EventHandler<CarEventArgs>(CarExploded); c1.Exploded += d;
...
}
■Source Code The PrimAndProperCarEvents (Generic) project is located under the Chapter 11 subdirectory.

370 CHAPTER 11 ■ DELEGATES, EVENTS, AND LAMBDAS
Understanding C# Anonymous Methods
Traditionally speaking, when a caller wishes to listen to incoming events, it must define a unique method that matches the signature of the associated delegate, for example:
class Program
{
static void Main(string[] args)
{
SomeType t = new SomeType();
//Assume "SomeDeletage" can point to methods taking no
//args and returning void.
t.SomeEvent += new SomeDelegate(MyEventHandler);
}
// Typically only called by the SomeDelegate object. public static void MyEventHandler()
{
// Do something when event is fired.
}
}
When you think about it, however, methods such as MyEventHandler() are seldom intended to be called by any part of the program other than the invoking delegate. As far as productivity is concerned, it is a bit of a bother (though in no way a showstopper) to manually define a separate method to be called by the delegate object.
To address this point, it is possible to associate a delegate directly to a block of code statements at the time of event registration. Formally, such code is termed an anonymous method. To illustrate the syntax, check out the following Main() method, which handles the events sent from the Car type using anonymous methods, rather than specifically named event handlers:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("***** Anonymous Methods *****\n");
Car c1 = new Car("SlugBug", 100, 10);
// Register event handlers as anonymous methods. c1.AboutToBlow += delegate {
Console.WriteLine("Eek! Going too fast!");
};
c1.AboutToBlow += delegate(object sender, CarEventArgs e) { Console.WriteLine("Message from Car: {0}", e.msg);
};
c1.Exploded += delegate(object sender, CarEventArgs e) { Console.WriteLine("Fatal Message from Car: {0}", e.msg);
};
...
}
}

CHAPTER 11 ■ DELEGATES, EVENTS, AND LAMBDAS |
371 |
■Note The final curly bracket of an anonymous method must be terminated by a semicolon. If you fail to do so, you are issued a compilation error.
Again, notice that the Program type no longer defines specific static event handlers such as CarAboutToBlow() or CarExploded(). Rather, the unnamed (aka anonymous) methods are defined inline at the time the caller is handling the event using the += syntax. The basic syntax of an anonymous method matches the following pseudo-code:
class Program
{
static void Main(string[] args)
{
SomeType t = new SomeType();
t.SomeEvent += delegate (optionallySpecifiedDelegateArgs) { /* statements */ };
}
}
When handling the first AboutToBlow event within the previous Main() method, notice that you are not specifying the arguments passed from the delegate:
c1.AboutToBlow += delegate { Console.WriteLine("Eek! Going too fast!");
};
Strictly speaking, you are not required to receive the incoming arguments sent by a specific event. However, if you wish to make use of the possible incoming arguments, you will need to specify the parameters prototyped by the delegate type (as shown in the second handling of the AboutToBlow and Exploded events). For example:
c1.AboutToBlow += delegate(object sender, CarEventArgs e) { Console.WriteLine("Critical Message from Car: {0}", e.msg);
};
Accessing “Outer”Variables
Anonymous methods are interesting in that they are able to access the local variables of the method that defines them. Formally speaking, such variables are termed outer variables of the anonymous method.
■Note An anonymous method cannot access ref or out parameters of the defining method.
Assume our Main() method defined a local integer named aboutToBlowCounter. Within the anonymous methods that handle the AboutToBlow event, we will increment this counter by 1 and print out the tally before Main() completes:
static void Main(string[] args)
{
...
int aboutToBlowCounter = 0;
// Make a car as usual.
Car c1 = new Car("SlugBug", 100, 10);