developmentor - a developer services company				  
				      C# Tutorial
   Modules

Delegates and Events


Goals:

  • Introduce the concept of a delegate.
  • Show how to define a delegate type.
  • Show how to register and unregister targets.
  • Show how to handle multiple targets.
  • Describe the effect of the event keyword.
  • Show some common applications of events from the .NET Framework Class Library.

Overview

A delegate is a proxy for a method. Clients interact with the delegate and the delegate forwards the operation to the method it represents. The advantage of this indirect arrangement is that the client code is relatively independent of the target method. The client sees only the delegate and does not need to know much about the method the delegate represents. Delegates are used in many places throughout the .NET Framework Class Library. Classic examples are as callbacks in user interface programming and as the way to specify the work to be performed by a thread.


Background - object-oriented method call

Let's first do a very quick review of a really basic concept. In an object-oriented system there are two components to a method call: the object and the method. Consider the Stock class and its Buy method shown below.

We must have an object of the Stock class available in order to call the Buy method. The object will become the hidden parameter this inside the method.


Notification

Objects typically store information in their fields. The formal way to say this is that the object maintains an internal "state". The state changes over time as user input is received or as client code calls methods. The Student class shown below models a student at a university and demonstrates the pattern. The student maintains a numerical rating of their scholarship in the form of a "gpa" (grade point average). Their gpa changes as they take courses and receive grades. Each time they finish a class, the RecordClass method will be called and their grade passed in (an A is worth 4 points, a B worth 3, and so on). A new value for the gpa will be calculated to take into account the newly received grade. In the example, we have made the simplifying assumption that each class is worth one unit.

Various parties might be interested in tracking the student's performance. Certainly the student's parents come to mind since they might be paying the tuition fees and want to stay informed of their child's progress. Other likely candidates are the school registrar, the dean of the college, any honor society for which gpa is a criterion of membership, etc. The student will be responsible for notifying all interested parties when the gpa changes. The situation is summarized in the diagram below.

Notification typically involves "registration" and "callback". The targets that would like to receive notification must register with the caller. The caller will notify all registered clients whenever the state changes. Another name for this pattern is "publish/subscribe". In our example, the parents and the registrar would need to register with the student. The student would notify them at the end of each class when their gpa is updated. The idea is captured in the figure below.


Delegate definition

.NET uses delegates to implement callbacks. A delegate acts as an intermediary between the caller and the target. The target creates a delegate and registers it with the caller. The caller invokes the delegate which forwards the call to the target. The main advantage to this approach is that the caller and the target do not communicate directly so the caller never needs to know the type of the target or the method that it is calling.

The caller and the target do need to know a little bit about each other since the caller is, in effect, making a method call on the target. The caller and target need to agree on the type of information that flows between them. In the student example, it makes sense for the student to send the new value for their gpa to a target such as a parent. We'll keep things simple and assume the parent doesn't need to send any information back to the student. If we think of these two pieces of information in terms of a method signature, we would picture a method that had an argument of type double (the gpa) and a return type of void (nothing gets passed back to the student). The information flowing between the student and their parent is described in the figure below.

For the student and parent to interact, they need a delegate that describes the information flowing between them. A delegate is defined using the C# keyword delegate in front of what otherwise looks like a normal method signature. The name of the delegate goes in the position normally occupied by the method name. The argument list and return type use the standard method definition syntax. A delegate for the student/parent interaction is shown below.

The compiler does a lot of work behind the scenes to process a delegate definition (typically generating an entire class from the delegate). The result of all the compiler's work is that a delegate name is actually a type name and we can declare references of that type and create objects.


Delegate use

Examine the diagram below that shows the links between the student, the delegate, and the parent.

The student needs to hold a reference to the delegate. To accomplish this, we simply add a field to the student class of the delegate type.

The parent needs to define a method for the student to call (indirectly through the delegate of course). The student and parent have agreed that the method should take a double argument and return void and this interaction was formalized in the delegate definition. The parent must then define a method that conforms to the required signature. The parent is free to name the method anything they like, only the parameter list and return type are constrained.

Next we write the client code that puts everything together. We need to create three objects: the student, the parent, and the delegate that links them. Creating the student and the parent are both straightforward operations. Creating the delegate is much more interesting. When a delegate is created it needs to be given the target object and the target method since both are necessary to make a method call in an object-oriented system. The target information is passed to the delegate constructor and will be stored inside the delegate object. Once the delegate is created, we register it with the student by assigning to the student's delegate field.

The student needs to call through the delegate whenever their gpa changes. To accomplish this, we modify the student's RecordClass method to include the invocation. The call is made using regular method call syntax on the delegate. Internally, the delegate calls the target method on the target object and passes along the gpa.


Null reference

A delegate is a reference type so delegate fields will default to null. It is common practice to test delegate fields to make sure they are not null before attempting to make a callback. The CLR will throw a NullReferenceException if the call is made when the reference is null.


Static methods

A static method can be registered using a delegate. When the delegate is created, simply pass ClassName.MethodName as the constructor argument. Because static methods do not have a this reference, a target object is needed.


Multiple targets

Multiple targets can be registered with a delegate. Each delegate stores an "invocation list" of targets and the + and += operators are used to add new targets to the list. All targets in the list get called back when the delegate is invoked. The targets will be called in the order they were added to the invocation list. The student class might use this feature to notify both their parents whenever their gpa changes. One minor point is worth mentioning. Notice that += is used to register both targets in the example below. This is ok even though when the first target is registered the delegate will be null. The special case is automatically handled to help make the client code simpler.

The - and -= operators are used to remove a target from an invocation list.


Events

There are several operations that can be performed on a delegate: assignment, registering a target with +=, removing a target with -=, and invocation. The common wisdom says that external code should be able to register a new target or unregister an existing target and should have no additional control over a delegate field. We do not want to allow external code to assign to a delegate because a careless client could accidentally remove all previously registered targets simply by using = instead of +=. We do not want to allow invocation because the external code is not in a position to know when conditions are right for a callback. The object internally should make this decision whenever its state changes. So we have a problem. If we make the delegate field public, the client code gets full control including the power of assignment and invocation. On the other hand, if we make the delegate field private the client code has no access at all and cannot even register or unregister targets.

This discussion is reminiscent of the private data / public access method pattern. The class designer could make the delegate field private and code public Register and Unregister methods. This would give the desired interface since clients could only add and remove targets and would have no additional power. The designers of C# decided that requiring programmers to constantly code that pattern was too much busywork. To solve the problem they introduced the event keyword. Adding event to a public delegate field gives exactly the desired behavior. External code can still use the += and -= operators but invocation and direct assignment will not compile.


Applications

The .NET Framework Class Library makes extensive use of events. For example, when creating a new Thread, the client must pass a ThreadStart delegate to the constructor. The delegate is used to specify the work that the new thread should perform. Another common application is in user interface programming. Controls such as buttons, text boxes, list boxes, etc. offer events. Clients create delegates and register them with the controls. When the event is triggered, the client will be called back through the delegate. To supply a concrete example of delegates and events in use, we will conclude our discussion here by taking a closer look at a few of the library delegate types and their use with GUI programming.

The .NET Framework Class Library defines a delegate named EventHandler which is used throughout the library. The EventHandler type is actually defined in the System namespace which indicates it is intended to be applicable to many areas of the library. The definition of the EventHandler delegate is shown below. Note how the delegate captures the information flowing between a publisher and a subscriber. A publisher passes itself as the first argument and an EventArgs object as the second argument. Passing itself to the handler method lets the client code easily determine which object fired the event. The EventArgs object is used to pass details of the event.

The data flow through an EventHandler delegate is summarized in the figure below.

The controls in the System.Windows.Forms namespace use an EventHandler for their Click event. The event is declared in the base class Control and is inherited by all derived controls such as the Button class. The figure below shows the definition of the event.

Clients that would like to register for the Click event must define a method with the appropriate signature: a first parameter of type object, a second parameter of type EventArgs, and a return type of void. They then create an EventHandler delegate and use the += operator to register with the event. The registration process is shown in the figure below. When the user clicks on the ok button in the GUI, the callback method will be invoked.

The Click event is a good introduction to the topic of GUI event handling but it is a little too simple to show the full power of the model. The main reason the Click event is not representative is that there is generally no need to pass any information from the control to the event handler for something as simple as a Click. That is, the only thing the handler needs to know is that the button was clicked and there is typically no need for more detailed data to be sent. In fact, the EventArgs class does not even contain any data so the publisher could not pass any additional information if it wanted to.

Other types of events do need to pass additional information. For example, a mouse event would likely need to send the coordinates of the mouse click and the identity of the mouse button the user pressed. To accommodate this type of interaction there are more specific delegate types that pass derived types of EventArgs as parameters. The derived types contain the detailed event information. A delegate definition for a mouse event is shown in the figure below. Notice how the second parameter is now MouseEventArgs rather than the more general EventArgs.

The MouseEventArgs class is derived from EventArgs and adds information specific to mouse events. Part of the class definition is shown below.

The Control class offers mouse events such as the MouseDown event shown in the next figure. Notice that the type of the event is MouseEventHandler rather than the generic EventHandler.

A client that wishes to subscribe to the MouseDown event must code a method that fits the MouseEventHandler pattern. The process is shown in the figure below.

There are other interesting events as well such as those for keyboard activity and painting. They all fit the same basic pattern: a specific delegate type with a derived class of EventArgs to transmit the event details.

This material is excerpted from the Programming C# course offered by DevelopMentor.