Chapter 12. Delegates and Events

When a head of state dies, the president of the United States typically doesn’t have time to attend the funeral personally. Instead, he dispatches a delegate. Often, this delegate is the vice president, but sometimes the VP is unavailable, and the president must send someone else, such as the secretary of state or even the first lady. He doesn’t want to “hardwire” his delegated authority to a single person; he might delegate this responsibility to anyone who is able to execute the correct international protocol.

The president defines in advance what responsibility will be delegated (attend the funeral), what parameters will be passed (condolences, kind words), and what value he hopes to get back (good will). He then assigns a particular person to that delegated responsibility at “runtime” as the course of his presidency progresses.

Events

In programming, you are often faced with situations where you need to execute a particular action, but you don’t know in advance which method, or even which object, you’ll want to call upon to execute it. The classic example of this is the method called to handle a button press, a menu selection, or some other “event.”

An event, in event-driven programming (like Windows!), is when something happens—often as a result of user action, but at times as a result of a change in system state or a result of a message begin received from outside the system (e.g., via the Internet).

You must imagine that the person who creates a button (or listbox or other control) will not necessarily be the programmer who uses the control. The control inventor knows that when the button is clicked, the programmer using the button will want something to happen, but the inventor can’t know what!

The solution to this dilemma is for the creator of the button to say, in effect, “My button publishes a series of events, such as click. My listbox has other events, such as selection changed, entry added, and so on. You programmers who want to use my controls, you can hook up whatever method you want to these events when you use my controls.” You hook up to these events using delegates.

A delegate is an object that contains the address of a method. That makes them useful for many purposes, but ideal for two:

  • “Call this method when this event happens” (event handling).

  • “Call this method when you’re done doing this work” (callbacks, discussed later in this chapter).

In C#, delegates are first-class members of the language (to a C++ programmer, they are Pointers To Member Functions on steroids!), and they are used to hook up an event to a method that will handle that event. In fact, events are just a restricted kind of delegate, as you’ll see later in this chapter.

Events and Delegates

In C#, delegates are fully supported by the language. Technically, a delegate is a reference type used to encapsulate a method with a specific signature and return type.[15] You can encapsulate any matching method in that delegate.

You create a delegate with the delegate keyword, followed by a return type and the signature of the methods that can be delegated to it, as in the following:

public delegate void ButtonClick(object sender, EventArgs e);

This declaration defines a delegate named ButtonClick, which will encapsulate any method that takes an object of type Object (the base class for everything in C#) as its first parameter and an object of type EventArgs (or anything derived from EventArgs) as its second parameter. The method will return void. The delegate itself is public.

Once the delegate is defined, you can encapsulate a member method with that delegate by instantiating the delegate, passing in a method that matches the return type and signature.

For example, you might define this delegate:

public delegate void buttonPressedHandler(object sender, EventArgs e);

That delegate could encapsulate either of these two methods:

public void onButtonPressed(object sender, EventArgs e)
{
    MessageBox.Show("the button was pressed!");
}

or:

public void myFunkyMethod(object s, EventArgs x)
{
     MessageBoxShow("Ouch!");
}

As you’ll see later, it can even encapsulate both at the same time! The key is that both methods return void and take two properties, an object and an EventArgs, as specified by the delegate.

Indirect Invocation

As an alternative, you can use anonymous methods as described later. In either case, the delegate can then be used to invoke that encapsulated method. In short, delegates decouple the class that declares the delegate from the class that uses the delegate: that is, the creator of the Button does not need to know how the Button will be used in every program that places a button on the page.

Publish and Subscribe/Observer

One very common “design pattern” in programming is that the creator of a control (such as a button) “publishes” (documents) the events to which the button will respond (such as click). Programmers who use the button (such as those who put a button on their form) may choose to “subscribe” to one or more of the button’s events. Thus, if you’re implementing a web form that uses a button, you might choose to subscribe (be notified) when someone clicks the button, but not when a mouse hovers over the button.

A closely related “pattern” is the observer pattern, in which the form is said to be the observer, and the button is said to be observed.

In any case, the mechanism for publishing is to create a delegate. The mechanism for subscribing is to create a method that matches the signature and return type of the delegate and then to subscribe your matching method with the syntax shown in Example 12-1.

The subscribing method is typically called an event handler, because it handles the event raised by the publishing class. That is, the form might handle the “click” event raised by the button.

By convention, event handlers in the .NET Framework return void and take two parameters. The first parameter is the “source” of the event (i.e., the publishing object). The second parameter is an object derived from EventArgs.

EventArgs is the base class for all event data. Other than its constructor, the EventArgs class inherits all its methods from Object, though it does add a public static field named Empty, which represents an event with no state (to allow for the efficient use of events with no state). The EventArgs derived class contains information about the event.

To make this less theoretical and more concrete, let’s look at Example 12-1, and then take it apart.

Example 12-1. Publish and subscribe with delegates
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace EventsWithDelegates
{
    // a class to hold the information about the event
    // in this case it will hold only information
    // available in the clock class, but could hold
    // additional state information
    public class TimeInfoEventArgs : EventArgs
    {
        public TimeInfoEventArgs(int hour, int minute, int second)
        {
            this.Hour = hour;
            this.Minute = minute;
            this.Second = second;
        }
        public readonly int Hour;
        public readonly int Minute;
        public readonly int Second;
    }

    // our subject -- it is this class that other classes
    // will observe. This class publishes one delegate:
    // SecondChanged.
    public class Clock
    {
        private int hour;
        private int minute;
        private int second;

        // the delegate
        public delegate void SecondChangeHandler
        (
            object clock,
            TimeInfoEventArgs timeInformation
        );

        // an instance of the delegate
        public SecondChangeHandler SecondChanged;

        protected virtual void SecondChanged(TimeInfoEventArgs e)
        {
            if (SecondChanged != null)
            {
                SecondChanged(this, e);
            }
        }



        // set the clock running
        // it will raise an event for each new second
        public void Run(  )
        {
            for (; ; )
            {
                // sleep 10 milliseconds
                Thread.Sleep(10);

                // get the current time
                System.DateTime dt = System.DateTime.Now;

                // if the second has changed
                // notify the subscribers
                if (dt.Second != second)
                {
                    // create the TimeInfoEventArgs object
                    // to pass to the subscriber
                    TimeInfoEventArgs timeInformation =
                    new TimeInfoEventArgs(
                    dt.Hour, dt.Minute, dt.Second);
                    SecondChanged(timeInformation);

                }

                // update the state
                this.second = dt.Second;
                this.minute = dt.Minute;
                this.hour = dt.Hour;
            }
        }
    }

    // an observer. DisplayClock subscribes to the
    // clock's events. The job of DisplayClock is
    // to display the current time
    public class DisplayClock
    {
        // given a clock, subscribe to
        // its SecondChangeHandler event
        public void Subscribe(Clock theClock)
        {
            theClock.SecondChanged +=
                new Clock.SecondChangeHandler(TimeHasChanged);
        }

        // the method that implements the
        // delegated functionality
        public void TimeHasChanged(
            object theClock, TimeInfoEventArgs ti)
        {
            Console.WriteLine("Current Time: {0}:{1}:{2}",
                                ti.Hour.ToString(  ),
                                ti.Minute.ToString(  ),
                                ti.Second.ToString(  ));
        }
    }

    // a second subscriber whose job is to write to a file
    public class LogCurrentTime
    {
        public void Subscribe(Clock theClock)
        {
            theClock.SecondChanged +=
                new Clock.SecondChangeHandler(WriteLogEntry);
        }

        // This method should write to a file.
        // We write to the console to see the effect.
        // This object keeps no state.
        public void WriteLogEntry(
        object theClock, TimeInfoEventArgs ti)
        {
            Console.WriteLine("Logging to file: {0}:{1}:{2}",
                                ti.Hour.ToString(  ),
                                ti.Minute.ToString(  ),
                                ti.Second.ToString(  ));
        }
    }

    public class Test
    {
        public static void Main(  )
        {
            // create a new clock
            Clock theClock = new Clock(  );

            // create the display and tell it to
            // subscribe to the clock just created
            DisplayClock dc = new DisplayClock(  );
            dc.Subscribe(theClock);

            // create a Log object and tell it
            // to subscribe to the clock
            LogCurrentTime lct = new LogCurrentTime(  );
            lct.Subscribe(theClock);

            // Get the clock started
            theClock.Run(  );
        }
    }
}

Partial Output...
Current Time: 16:0:7
Logging to file: 16:0:7
Current Time: 16:0:8
Logging to file: 16:0:8
Current Time: 16:0:9
Logging to file: 16:0:9
Current Time: 16:0:10
Logging to file: 16:0:10
Current Time: 16:0:11
Logging to file: 16:0:11
Current Time: 16:0:12
Logging to file: 16:0:12

The Publishing Class: Clock

The class that we will be observing is Clock. It publishes one event, SecondChanged. The syntax for publishing that event is that it declares a delegate, SecondChangedHandler, that must be subscribed to by anyone interested in being notified when the second changes:

public delegate void SecondChangeHandler
(
    object clock,
    TimeInfoEventArgs timeInformation
);

As is typical for all event handlers in .NET, this delegate returns void and takes two arguments: the first of type void, and the second of type EventArgs (or, as in this case, of a type derived from EventArgs).

Let’s delay looking at TimeInfoEventArgs for just a bit and continue on.

The Clock class must then create an instance of this delegate, which it does on the following line:

public SecondChangeHandler SecondChanged;

You read this as “The member variable SecondChanged is an instance of the delegate SecondChangeHandler.”

The third thing that the Clock will do is to provide a protected method for invoking its event. The event will be invoked only if there are subscribers (again, I’ll show how other classes subscribe in just a moment):

protected virtual void SecondChanged(TimeInfoEventArgs e)
{
    if (SecondChanged != null)
    {
        SecondChanged(this, e);
    }
}

You can infer from this that if no one has subscribed, the instance SecondChanged will be null, but if any other class has subscribed, it will not be null, and the method that the other classes have registered will be invoked through the delegate by calling the instance of the delegate and passing in the Clock (this) and the instance of TimeInfoEventArgs that was passed into SecondChanged.

So, who calls SecondChanged?

It turns out that Clock has another method, Run, that keeps an eye on the clock, and every time the seconds change, it makes a new instance of TimeInfoEventArgs and calls SecondChanged:

public void Run(  )
{
    for (; ; )
    {
        // sleep 10 milliseconds
        Thread.Sleep(10);

        // get the current time
        System.DateTime dt = System.DateTime.Now;

        // if the second has changed
        // notify the subscribers
        if (dt.Second != second)
        {
            // create the TimeInfoEventArgs object
            // to pass to the subscriber
            TimeInfoEventArgs timeInformation =
            new TimeInfoEventArgs(
            dt.Hour, dt.Minute, dt.Second);

            SecondChanged(timeInformation);

        }

        // update the state
        this.second = dt.Second;
        this.minute = dt.Minute;
        this.hour = dt.Hour;
    }
}

Run consists of a “forever” loop (it never stops until you shut down the program). It sleeps for 10 milliseconds (so as not to bring your computer to a complete halt), and then checks the system time against the hour, minute, and second that it stores in member variables. If there is a change, it creates a new instance of the TimeInfoEventArgs object, and then invokes SecondChanged, passing in that object.

OK, here’s the definition of TimeInfoEventArgs:

public class TimeInfoEventArgs : EventArgs
{
    public readonly int Hour;
    public readonly int Minute;
    public readonly int Second;
    public TimeInfoEventArgs(int hour, int minute, int second)
    {
        this.Hour = hour;
        this.Minute = minute;
        this.Second = second;
    }

}

Registering to Be Notified

We’re almost ready to walk through the scenario, but we still don’t know how classes register to be notified. To see this, we need to create two subscribers, DisplayClock and LogCurrentTime.

DisplayClock, we’ll pretend, is a nice digital clock that sits on your desktop, and LogCurrentTime is a nifty utility you can invoke when you want to log an error and have it timestamped. Cool, eh? Wish we were really going to write that.

But we’re not:

public class DisplayClock
{
    public void Subscribe(Clock theClock)
    {
        theClock.SecondChanged +=
            new Clock.SecondChangeHandler(TimeHasChanged);
    }

    public void TimeHasChanged(
        object theClock, TimeInfoEventArgs ti)
    {
        Console.WriteLine("Current Time: {0}:{1}:{2}",
                            ti.Hour.ToString(  ),
                            ti.Minute.ToString(  ),
                            ti.Second.ToString(  ));
    }
}

You pass a Clock object into the Subscribe method of DisplayClock, and it registers itself to be notified. How? It uses the += operator on the delegate it wants to register with (secondChanged), creating a new instance of the delegate, passing to the constructor the name of a method within DisplayClock that matches the return value (void) and the parameters (object, TimeEventArgs). DisplayClock just happens to have such a method: TimeHasChanged.

This serendipitous method does not display the time nicely on your computer, but it does display it, using the Hour, Minute, and Second it retrieves from the TimeInfoEventArgs it gets back when notified that the time has changed!

The second subscriber is much more sophisticated:

public class LogCurrentTime
{
    public void Subscribe(Clock theClock)
    {
        theClock.SecondChanged +=
            new Clock.SecondChangeHandler(WriteLogEntry);
    }

    public void WriteLogEntry(
    object theClock, TimeInfoEventArgs ti)
    {
        Console.WriteLine("Logging to file: {0}:{1}:{2}",
                            ti.Hour.ToString(  ),
                            ti.Minute.ToString(  ),
                            ti.Second.ToString(  ));
    }
}

Here, we pretend to write to a logfile the Hour, Minute, and Second that we get from the TimeInfoEventArgs object, but we don’t really do that either; we just write it to the screen.

So, how do our two subscribers get their clock instances? Easy—we pass the Clock object in right after we create them, in Main( ):

public static void Main(  )
{
    // create a new clock
    Clock theClock = new Clock(  );

    // create the display and tell it to
    // subscribe to the clock just created
    DisplayClock dc = new DisplayClock(  );
    dc.Subscribe(theClock);

    // create a Log object and tell it
    // to subscribe to the clock
    LogCurrentTime lct = new LogCurrentTime(  );
    lct.Subscribe(theClock);

    // Get the clock started
    theClock.Run(  );
}

Sequence of Events

Here is the sequence of events (which, as you might suspect already, is the reverse order of that in which I explained them!).

In Main( ), we create a Clock, cleverly named theClock. A clock knows how to publish the time, if anyone subscribes.

We then create an instance of a DisplayClock (named dc), which is quite good at subscribing to the Clock, and we tell it to Subscribe, passing in our new Clock so that it may do so.

We also create an instance of a LogCurrentTime class (lct), and tell it to Subscribe to theClock, which it does.

Finally, now that two listeners are paying attention, we tell theClock to Run. Every second, theClock realizes that a second has passed, so it creates a new TimeInfoEventArgs object and calls SecondChanged. SecondChanged tests whether the delegate is null, which it is not because two listeners have registered, so it fires the delegate:

SecondChanged(this, e);

This is exactly as though it had reached into DisplayClock and called TimeHasChanged, passing in a copy of itself and a copy of the newly minted TimeInfoEventArgs, and then reached into LogCurrentTime and called WriteLogEntry, passing in the same two arguments!

The Danger with Delegates

There is a problem with Example 12-1, however. What if the LogCurrentTime class was not so considerate, and it used the assignment operator (=) rather than the subscribe operator (+=), as in the following:

public void Subscribe(Clock theClock)
{
    theClock.SecondChanged=
    new Clock.SecondChangeHandler(WriteLogEntry);
}

If you make that one tiny change to the example, you’ll find that the Logger( ) method is called, but the DisplayClock method is not called. The assignment operator replaced the delegate held in the SecondChanged multicast delegate. The technical term for this is bad.

A second problem is that other methods can call SecondChangeHandler directly. For example, you might add the following code to the Main( ) method of your Test class:

Console.WriteLine("Calling the method directly!");
System.DateTime dt = System.DateTime.Now.AddHours(2);

TimeInfoEventArgs timeInformation =
    new TimeInfoEventArgs(
    dt.Hour,dt.Minute,dt.Second);

theClock.SecondChanged(theClock, timeInformation);

Here, Main( ) has created its own TimeInfoEventArgs object and invoked SecondChanged directly. This runs fine, even though it is not what the designer of the Clock class intended. Here is the output:

Calling the method directly!
Current Time: 18:36:7
Logging to file: 18:36:7
Current Time: 16:36:7
Logging to file: 16:36:7

The problem is that the designer of the Clock class intended the methods encapsulated by the delegate to be invoked only when the event is fired. Here, Main( ) has gone around through the back door and invoked those methods itself. What is more, it has passed in bogus data (passing in a time construct set to two hours into the future!).

How can you, as the designer of the Clock class, ensure that no one calls the delegated method directly? You can make the delegate private, but then it won’t be possible for clients to register with your delegate at all. What you need is a way to say, “This delegate is designed for event handling: you may subscribe and unsubscribe, but you may not invoke it directly.”

The event Keyword

The solution to this dilemma is to use the event keyword. The event keyword indicates to the compiler that the delegate can be invoked only by the defining class, and that other classes can only subscribe to and unsubscribe from the delegate using the appropriate += and -= operators, respectively.

To fix your program, change your definition of SecondChanged from:

public SecondChangeHandler SecondChanged;

to the following:

publicevent SecondChangeHandler SecondChanged;

Adding the event keyword fixes both problems. Classes can no longer attempt to subscribe to the event using the assignment operator (=), as they could previously, nor can they invoke the event directly, as was done in Main( ) in the preceding example. Either of these attempts will now generate a compile error:

The event 'Programming_CSharp.Clock.SecondChanged' can only appear on
the left hand side of += or -= (except when used from within the type
'Programming_CSharp.Clock')

There are two ways to look at SecondChanged now that you’ve modified it. In one sense, it is simply a delegate instance to which you’ve restricted access using the keyword event. In another, more important sense, SecondChanged is an event, implemented by a delegate of type SecondChangeHandler. These two statements mean the same thing, but the latter is a more object-oriented way to look at it and better reflects the intent of this keyword: to create an event that your object can raise, and to which other objects can respond.

Example 12-2 shows the complete source, modified to use the event rather than the unrestricted delegate.

Example 12-2. Using the event keyword
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace EventsWithDelegates
{
    public class TimeInfoEventArgs : EventArgs
    {
        public readonly int Hour;
        public readonly int Minute;
        public readonly int Second;
        public TimeInfoEventArgs(int hour, int minute, int second)
        {
            this.Hour = hour;
            this.Minute = minute;
            this.Second = second;
        }
    }

    public class Clock
    {
        private int hour;
        private int minute;
        private int second;

        public delegate void SecondChangeHandler
        (
            object clock,
            TimeInfoEventArgs timeInformation
        );

        // public SecondChangeHandler SecondChanged;
        public event SecondChangeHandler SecondChanged;

        protected virtual void SecondChanged(TimeInfoEventArgs e)
        {
            if (SecondChanged != null)
            {
                SecondChanged(this, e);
            }
        }

        public void Run(  )
        {
            for (; ; )
            {
                Thread.Sleep(10);

                System.DateTime dt = System.DateTime.Now;

                if (dt.Second != second)
                {
                    TimeInfoEventArgs timeInformation =
                    new TimeInfoEventArgs(
                    dt.Hour, dt.Minute, dt.Second);
                    SecondChanged(timeInformation);
                }

                this.second = dt.Second;
                this.minute = dt.Minute;
                this.hour = dt.Hour;
            }
        }
    }

    public class DisplayClock
    {
        public void Subscribe(Clock theClock)
        {
            theClock.SecondChanged +=
                new Clock.SecondChangeHandler(TimeHasChanged);
        }

        public void TimeHasChanged(
            object theClock, TimeInfoEventArgs ti)
        {
            Console.WriteLine("Current Time: {0}:{1}:{2}",
                 ti.Hour.ToString(  ),
                 ti.Minute.ToString(  ),
                 ti.Second.ToString(  ));
        }
    }

    public class LogCurrentTime
    {
        public void Subscribe(Clock theClock)
        {
            //theClock.SecondChanged =
            //    new Clock.SecondChangeHandler(WriteLogEntry);

            theClock.SecondChanged +=
                new Clock.SecondChangeHandler(WriteLogEntry);

        }

        public void WriteLogEntry(
        object theClock, TimeInfoEventArgs ti)
        {
            Console.WriteLine("Logging to file: {0}:{1}:{2}",
                    ti.Hour.ToString(  ),
                    ti.Minute.ToString(  ),
                    ti.Second.ToString(  ));
        }
    }

    public class Test
    {
        public static void Main(  )
        {
            Clock theClock = new Clock(  );


           DisplayClock dc = new DisplayClock(  );
            dc.Subscribe(theClock);


           LogCurrentTime lct = new LogCurrentTime(  );
            lct.Subscribe(theClock);


            //Console.WriteLine("Calling the method directly!");
            //System.DateTime dt = System.DateTime.Now.AddHours(2);

            //TimeInfoEventArgs timeInformation =
            //    new TimeInfoEventArgs(
            //    dt.Hour, dt.Minute, dt.Second);

            //theClock.SecondChanged(theClock, timeInformation);



           theClock.Run(  );
       }
    }
}

Anonymous Methods

In the preceding example, you subscribed to the event by invoking a new instance of the delegate, passing in the name of a method that implements the event:

theClock.SecondChanged +=
    new Clock.SecondChangeHandler(TimeHasChanged);

You can also assign this delegate by writing the shortened version:

theClock.SecondChanged += TimeHasChanged;

Later in the code, you must define TimeHasChanged as a method that matches the signature of the SecondChangeHandler delegate:

public void TimeHasChanged(object theClock,TimeInfoEventArgsti)
{
    Console.WriteLine("Current Time: {0}:{1}:{2}",
                       ti.Hour.ToString(  ),
                       ti.Minute.ToString(  ),
                       ti.=Second.ToString(  ));
}

C# offers anonymous methods that allow you to pass a code block rather than the name of the method. This can make for more efficient and easier-to-maintain code, and the anonymous method has access to the variables in the scope in which they are defined:

clock.SecondChanged += delegate( object theClock,TimeInfoEventArgs ti )
{
    Console.WriteLine( "Current Time: {0}:{1}:{2}",
                       ti.Hour.ToString(  ),
                       ti.Minute.ToString(  ),
                       ti.Second.ToString(  ) );
};

Warning

Overused, this can also make for cut-and-paste code that is harder to maintain.

Notice that instead of registering an instance of a delegate, you use the keyword delegate, followed by the parameters that would be passed to your method, followed by the body of your method encased in braces and terminated by a semicolon.

This “method” has no name; hence, it is anonymous. You can invoke the method only through the delegate, but that is exactly what you want.

Lambda Expressions

C# 3.0 extends the concept of anonymous methods and introduces lambda expressions, which are more powerful and flexible than anonymous methods.

Tip

Lambda expressions are designed to provide not only inline delegate definitions, but also a framework for Language-Integrated Query (LINQ). We discuss LINQ in detail in Chapter 13 and Chapter 15.

You define a lambda expression using this syntax:

(input parameters) => {expression or statement block};

The lambda operator, =>, is newly introduced in C# 3.0 and is read as “goes to.” The left operand is a list of zero or more input parameters, and the right operand is the body of the lambda expression.

You can thus rewrite the delegate definition as follows:

theClock.SecondChanged +=
    (aClock, ti) =>
    {
        Console.WriteLine("Current Time: {0}:{1}:{2}",
                           ti.hour.ToString(  ),
                           ti.minute.ToString(  ),
                           ti.second.ToString(  ));
    };

You read this as "theClock’s SecondChanged delegate adds an anonymous delegate defined by this lambda expression. The two parameters, aClock and ti, go to the WriteLine expression that writes out the hour and minute and second from ti.”

The two input parameters, aClock and ti, are of type Clock and TimeInfoEventArgs, respectively. Their types are not specified because the C# compiler infers their types from the SecondChanged delegate definition. If the compiler is unable to infer the types of your operands, you may specify them explicitly:

(Clock aClock, TimeInfoEventArgs ti) => {...};

If there is no input parameter, you write a pair of empty parentheses:

(  ) => {expression or statement block};

If there is only one input parameter, the parentheses can be omitted:

n => n * n;

Callback Methods

The second classic use for a delegate is a callback. When you go to your favorite restaurant at 8 p.m. on a Saturday, the wait may be quite long. You give your name and they give you a pager. When your table is ready, they dial a number that buzzes your pager, and that signals you that your table is ready. Callbacks work the same way, only this time, the table is your data, the pager is a method, and the phone number they call to make the pager buzz is a delegate!

Let’s say you reach a place in your program where you need to get data from a web service (a program running on another computer that you do not control, and that is out there on the Internet “somewhere”). You can’t know how long it will take to get your data, but it might be a very long time, a second or more. Rather than having your program wait in the crowded lobby, listening to annoying muzak, you hand over a delegate to the method that should be called when your table (excuse me, when your data) is ready. When the web service returns with the data, your delegate is invoked, and because it has the address of your method, the method is called, which takes action on the data returned.

You implement callbacks with the AsyncCallback delegate, which allows the main work of your program to continue until “your table is ready”:

[Serializable]
public delegate void AsyncCallback
(
    IAsyncResult ar
);

The attribute (Serializable) is covered in Chapter 22. You can see here, however, that AsyncCallback is a delegate for a method that returns void and takes a single argument, an object of type IAsyncResult. The Framework defines this interface, and the CLR will be calling your method with an object that implements IAsyncResult, so you don’t need to know the details of the interface; you can just use the object provided to you.

Here’s how it works. You will ask the delegate for its invocation list, and you will call BeginInvoke on each delegate in that list. BeginInvoke will take two parameters. The first will be a delegate of type AsyncCallback, and the second will be your own delegate that invokes the method you want to call:

del.BeginInvoke(new AsyncCallback(ResultsReturned),del);

In the preceding line of code, you are calling the method encapsulated by del (e.g., DisplayCounter), and when that method completes, you want to be notified via your method ResultsReturned.

The method to be called back (ResultsReturned) must match the return type and signature of the AsyncCallback delegate: it must return void, and must take an object of type IAsyncResult:

private void ResultsReturned(IAsyncResult iar)
{

When that method is called back, the .NET Framework passes in the IAsyncResult object. The second parameter to BeginInvoke is your delegate, and that delegate is stashed away for you in the AsyncState property of the IAsyncResult as an Object. Inside the ResultsReturned callback method, you can extract that Object and cast it to its original type:

DelegateThatReturnsInt del = (DelegateThatReturnsInt)iar.AsyncState;

You can now use that delegate to call the EndInvoke( ) method, passing in the IAsyncResult object you received as a parameter:

int result = del.EndInvoke(iar);

EndInvoke( ) returns the value of the called (and now completed) method, which you assign to a local variable named result, and which you are now free to display to the user.

The net effect is that in Run( ), you get each registered method in turn (first FirstSubscriber.DisplayCounter and then SecondSubscriber.Doubler), and you invoke each asynchronously. There is no delay between the call to the first and the call to the second, as you aren’t waiting for DisplayCounter to return.

When DisplayCounter (or Doubler) has results, your callback method (ResultsReturned) is invoked, and you use the IAsyncResult object provided as a parameter to get the actual results back from these methods. Example 12-3 shows the complete implementation.

Example 12-3. Asynchronous invocation of delegates
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace AsyncDelegates
{
    public class ClassWithDelegate
    {
        // a multicast delegate that encapsulates a method
        // that returns an int
        public delegate int DelegateThatReturnsInt(  );
        public DelegateThatReturnsInt theDelegate;

        public void Run(  )
        {
            for (; ; )
            {
                // sleep for a half second
                Thread.Sleep(500);

                if (theDelegate != null)
                {
                    // explicitly invoke each delegated method
                    foreach (
                    DelegateThatReturnsInt del in
                    theDelegate.GetInvocationList(  ))
                    {
                        // invoke asynchronously
                        // pass the delegate in as a state object
                        del.BeginInvoke(new AsyncCallback(ResultsReturned),
                        del);

                    } // end foreach
                } // end if
            } // end for ;;
        } // end run

        // callback method to capture results
        private void ResultsReturned(IAsyncResult iar)
        {
            // cast the state object back to the delegate type
            DelegateThatReturnsInt del =
            (DelegateThatReturnsInt)iar.AsyncState;

            // call EndInvoke on the delegate to get the results
            int result = del.EndInvoke(iar);

            // display the results
            Console.WriteLine("Delegate returned result: {0}", result);
        }
    } // end class

    public class FirstSubscriber
    {
        private int myCounter = 0;

        public void Subscribe(ClassWithDelegate theClassWithDelegate)
        {
            theClassWithDelegate.theDelegate +=
            new ClassWithDelegate.DelegateThatReturnsInt(DisplayCounter);
        }

        public int DisplayCounter(  )
        {
            Console.WriteLine("Busy in DisplayCounter...");
            Thread.Sleep(10000);
            Console.WriteLine("Done with work in DisplayCounter...");
            return ++myCounter;
        }
    }

    public class SecondSubscriber
    {
        private int myCounter = 0;

        public void Subscribe(ClassWithDelegate theClassWithDelegate)
        {
            theClassWithDelegate.theDelegate +=
            new ClassWithDelegate.DelegateThatReturnsInt(Doubler);
        }

        public int Doubler(  )
        {
            return myCounter += 2;
        }
    }

    public class Test
    {
        public static void Main(  )
        {
            ClassWithDelegate theClassWithDelegate =
            new ClassWithDelegate(  );

            FirstSubscriber fs = new FirstSubscriber(  );
            fs.Subscribe(theClassWithDelegate);

            SecondSubscriber ss = new SecondSubscriber(  );
            ss.Subscribe(theClassWithDelegate);

            theClassWithDelegate.Run(  );
        }
    }
}


[15] * If the method is an instance method, the delegate encapsulates the target object as well.

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

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