Chapter 9. Events and Delegates

IN THIS CHAPTER

Delegates and events are a key part of developing applications in C#. Developers often mistakenly assume that event-based programming is the sole domain of Windows Forms programming. The truth is that you can add a lot of power and value to your applications by making use of events and delegates, regardless of whether you’re creating a Windows Forms application, an ASP.NET application, or even an application with no user interface at all.

This chapter shows you how to start developing with delegates, anonymous methods, multicast delegates, and events. At the end of the chapter, you will be able to start adding event-based programming patterns to your applications as you continue throughout this book.

Introduction to Delegates

A delegate is a special type that maintains a reference to a method. After a delegate has been assigned to an actual method that matches the signature defined by the delegate itself, the delegate can be used the same way the real method can be used. You can execute the method by executing the delegate, pass parameters, and so on.

As long as the method’s signature matches the signature defined by the delegate, the method and associated parameters can be assigned to the delegate. This allows you to programmatically change methods and add additional methods to existing classes.

Delegates can also be passed as parameters, making them perfect for callback solutions. A callback is where a method is passed a delegate parameter indicating a method that should be called back periodically. For example, you could have a long-running method invoke the callback method periodically to update a progress bar.

Delegate declarations look much like a method signature, preceded by the keyword delegate, as shown in the following examples:

delegate void MessagePrintDelegate(string msg);
delegate int GetCountDelegate(object obj1, object obj2);
delegate void LongRunningDelegate(MessagePrintDelegate mpCallBack);

A delegate can have scope just like any other member variable; however, you cannot apply the static keyword to a delegate. As such, you can rewrite the preceding declarations as shown in the following example:

public delegate void MessagePrintDelegate(string msg);
private delegate int GetCountDelegate(object obj1, object obj2);
internal delegate void LongRunningDelegate(MessagePrintDelegate mpCallBack);

When you have a delegate defined, you can create an instance of the delegate, assign a method to it (as long as the signature matches), and pass it as a parameter to other methods. The code in Listing 9.1 illustrates some basic techniques for working with delegates.

Listing 9.1 Basic Delegate Manipulation

Image

Image

When you run the preceding code, you get the output shown in Figure 9.1.

Figure 9.1 Output of simple delegate demo.

Image

Covariance and Contravariance

Covariance and contravariance might seem like pretty complex words, but they represent some fairly easy-to-understand concepts. Covariance refers to the ability of a delegate method to return derivative data types. In the preceding example, you’ll notice that the Contact class derives from Person. Suppose that you have a delegate defined as follows:

delegate Person GetPersonDelegate();

Then covariance allows for the following lines of code to work properly:

GetPersonDelegate gpd = new GetPersonDelegate(GetPerson);
GetPersonDelegate gpd2 = new GetPersonDelegate(GetContact);

This sample shows that covariance allows you to create an instance of the GetPersonDelegate for a method that returns a Person instance as well as a method that returns a Contact instance.

Contravariance refers to the ability of a delegate method to take derivative classes as parameters. In Listing 9.1 you saw the delegate GetCountDelegate, which takes two parameters of type Person. Contravariance allows you to pass parameters of derivative types to delegate methods, as shown in the following example:

Contact c = new Contact();
Person p = new Person();
int x = gcd(p, p);
int y = gcd(c, c);

Using Anonymous Methods

In the example in Listing 9.1, there is a method defined on the class for each delegate. In other words, when an instance of the delegate is created, the name of the method is passed to the constructor, as shown following:

MessagePrintDelegate mpDel = new MessagePrintDelegate(PrintMessage);

Here, PrintMessage is the name of a method that has been defined on the class. Depending on your needs and the type of programming you are doing, it could become extremely tedious and inefficient to code a separate method for each delegate instance that you create.

Anonymous methods allow you to essentially define your method “on the fly” at the same time that you are instantiating the delegate. Anonymous methods are also referred to as “inline methods” by other programming languages.

Another advantage of anonymous methods that sets them apart from using fixed method definitions is that the code written inside your anonymous method has access to variables in the same scope as the definition. This is extremely powerful because it eliminates the need to pass unnecessary or redundant data into the fixed method itself (thereby needlessly bloating the delegate’s definition) by allowing the anonymous method to access the data directly.

Listing 9.2 illustrates the use of anonymous methods compared with the use of a fixed method.

Listing 9.2 Anonymous Methods Sample

Image

Image

Figure 9.2 shows the output of the anonymous method sample.

Figure 9.2 Output of anonymous method sample.

Image

Creating Multicast Delegates

A multicast delegate is a delegate that can invoke multiple methods, so long as the methods all have the same signature as the delegate’s definition. Multicast delegates are the steppingstone between standard delegates and the use of events. When you understand both delegates and multicast delegates, you will be ready to start working with events.

You create a multicast delegate through composition. Composing multiple delegates is accomplished with the +, -, +=, and -= operators. To make a delegate invoke multiple methods, you simply add the method to an existing delegate using + or +=. To remove a method from a multicast delegate, you use the – or the -= overloaded operators.

The code in Listing 9.3 provides an illustration of how to create and invoke multicast delegates. In addition, the sample also shows creating a multicast delegate from both named and anonymous methods, and how to iterate through the list of methods that will be invoked by a multicast delegate.

Listing 9.3 Multicast Delegate Sample

Image

Image

When you run the preceding code, the output looks as follows, illustrating how a single invocation to a multicast delegate can invoke the target of that delegate multiple times:

Image

As you can see, multicast delegates are an extremely powerful feature with virtually unlimited applications. Events, which are one of those applications, are discussed next. It is also worth noting that in the preceding output, the anonymous method has been assigned a name by the CLR: <Main>b_ _0. All methods must be identified by a name, even anonymous methods, so the CLR generates a name dynamically based on the scope and sequence of the method.

Introduction to Events

Events are a way to allow a class to send a signal indicating that an event of some importance has taken place. Events are most commonly used in the Windows Forms user interface, sending signals indicating that the user has clicked on a button, typed characters, moved the mouse, or any number of other events.

Events can also be used to indicate other important events that don’t have anything to do with a user interface, such as indicating that an object’s internal state has changed, that data in a list has changed, and so on.

Events are created from delegates using the event keyword. First, you declare the delegate:

delegate void StateChangedDelegate(object state);

Then you declare the event as a member of a class:

public event StateChangedDelegate OnStateChanged;

Finally, the class interested in the event can subscribe to the event in a syntax that should look familiar to you given your recent exposure to multicast delegates:

otherClass.OnStateChanged += new StateChangedDelegate(OtherClass_StateChanged);

Now the subscribing class will be notified every time the other class’s state changes.

The sample in Listing 9.4 shows a class called LongTask. This class has a method called PerformTask. This method simulates a long-running process. The class exposes an event called OnProgress that allows a subscribing class to be notified when progress on the long-running task has been made. This example also makes use of event arguments to show good design patterns that will look very familiar to you when you start working with Windows Forms.

Listing 9.4 Event Publishing and Subscribing Sample

Image

Image

When you run the preceding code, you will see 100 lines of text, each one reporting that another percentage point of work has been completed on the simulated long-running task.

One point about event-based programming that can’t be stressed enough is that by using events, you allow a loose coupling of classes and maximize your code reuse.


Tip

A bad way to create the code in Listing 9.3 would be to write the code that performs the task directly in a Windows Form so that the code could interact directly with the user interface to indicate progress made by possibly changing the properties of a ProgressBar control. The problem that arises from that design is that the long-running task can then never be used anywhere but within that one form. If you want to perform that same task in a Web Service, a console application, a Windows Service, or even in another Windows Forms application, you will have to cut and paste code and perform some tedious tasks to get it to work.


By using events, you can then have one piece of code that is responsible for performing the task, and you could have the user interface environment subscribe to the progress event. This loose coupling allows you to reuse your code without any modification in any GUI environment.

Advanced Event-Based Programming

At this point, if the discussion of programming ended with events, you would have enough information to continue through the rest of the book and nothing that Windows Forms or ASP.NET do related to events would seem unfamiliar to you.

Many techniques that can be applied to event programming will produce some pretty amazing results. For example, one such technique is the use of the add and remove accessors on an event. Just as properties have a get and set accessor, events have their own accessors that allow you to write code that will be executed when a client subscribes to an event and when a client unsubscribes from the event. The power that these accessors give your code is incredible. For example, if you have written some code that exposes events over a network using Remoting, you can write code inside the “add” accessor that allows you to detect when a network client subscribes to an event. Likewise, you can detect when a network client unsubscribes, giving you immediate access to that information.

There also may be scenarios when you don’t want an event to be fired to all subscribing clients. Take the hypothetical scenario where you have created an event-driven application. The application consists of a central server and multiple clients. Each client subscribes to an event that indicates some data has changed. However, this data is secured at the row level, and not all clients should be able to see it. To maximize security, your application is not even allowed to transmit data over a wire that the client cannot see. To accomplish this using event-based programming, you need to be able to programmatically filter which event targets receive the event and which targets do not.

Each delegate has a Target property, which indicates the object that houses the method that will be invoked by the delegate. A trick that I find extremely helpful is that if the code knows the data type of the subscribing class, it can use that information to determine if the delegate should be invoked on that object or not, as shown in the following snippet of code:

if (  ((Customer)myDelegate.Target).CustomerID == 12)
    // invoke method

The code in Listing 9.5 is a complete sample that demonstrates the use of the add and remove accessors, as well as the selective invocation of methods on event subscribers.

Listing 9.5 Advanced Event Programming Sample

Image

Image

Image

Image

Image

The first thing that should be noted about the preceding code is that the add and remove accessors are used to print out some informative messages indicating when a class subscribes or unsubscribes from the event. Also note that in these accessors, the Target property is used to print out the SubscriberID of the class subscribing to or unsubscribing from the event.

Next, the NotifyAllBut method will invoke all the subscribing methods to an event except the event handler hosted by a Subscriber class whose subscriber ID is 'subscriber-C'. This is proven in the console output of the program shown in the following lines:

Image

As you can see from this output, all three subscribers subscribe to the OnNameChanged event. All three of them receive a notification indicating that the first name and last name changed. Then, after subscriber B unsubscribes, only subscribers A and C receive the notification that the first name was changed. Finally, only subscriber A receives notification that the middle name changed because subscriber C was specifically prevented from receiving the event.

Summary

This chapter has introduced you to some of the basic concepts around event-based programming as well as many advanced topics. Delegates are type-safe references to methods that can be invoked in the same manner as methods and they can also be used as parameters to other methods.

Events are special multicast delegates that provide a mechanism by which classes can notify other classes when events of significance take place, whether those events are related to the user interface, to data sources, or to object state.

In the last section of the chapter you saw that event programming can be extremely powerful by allowing developers to write their own code for the add and remove accessors for an event and allowing an event to selectively choose which targets receive the event notification.

When you consider all of this information and add to it the fact that events can be transmitted between GUI controls, between logical tiers or layers, and even between different applications separated by the Internet or an intranet, the idea of adopting and using events in your everyday programming tasks becomes extremely appealing.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.217.248.216