Chapter 7. Delegates and Events

Two of the most important aspects of object-oriented programming are delegates and events. A delegate basically enables you to reference a function without directly invoking the function. Delegates are often used to implement techniques called callbacks, which means that after a function has finished execution, a call is made to a specific function to inform it that the execution has completed. In addition, delegates are also used in event handling. Despite the usefulness of delegates, it is a topic that not all .NET programmers are familiar with. An event, on the other hand, is used by classes to notify clients when something of interest has happened. For example, a Button control has the Click even, which allows your program to be notified when someone clicks the button.

This chapter explores the following:

  • What is a delegate?

  • Using delegates

  • Implementing callbacks using a delegate

  • What are events?

  • How to handle and implement events in your program

Delegates

In C#, a delegate is a reference type that contains a reference to a method. Think of a delegate as a pointer to a function. Instead of calling a function directly, you use a delegate to point to it and then invoke the method by calling the delegate. The following sections explain how to use a delegate and how it can help improve the responsiveness of your application.

Creating a Delegate

To understand the use of delegates, begin by looking at the conventional way of invoking a function. Consider the following program:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Delegates
{
    class Program
    {
        static void Main(string[] args)
        {
            int num1 = 5;
            int num2 = 3;
            Console.WriteLine(Add(num1, num2).ToString());
            Console.WriteLine(Subtract(num1, num2).ToString());
            Console.ReadLine();
        }

        static int Add(int num1, int num2)
        {
            return (num1 + num2);
        }

        static int Subtract(int num1, int num2)
        {
            return (num1 - num2);
        }
    }
}

The program contains three methods: Main(), Add(), and Subtract(). Notice that the Add() and Subtract() methods have the same signature. In the Main() method, you invoke the Add() and Subtract() methods by calling them directly, like this:

Console.WriteLine(Add(num1, num2).ToString());
            Console.WriteLine(Subtract(num1, num2).ToString());

Now create a delegate type with the same signature as the Add() method:

namespace Delegates
{
    class Program
    {
        delegate int MethodDelegate(int num1, int num2);

        static void Main(string[] args)
        {
         ...

You define a delegate type by using the delegate keyword, and its declaration is similar to that of a function, except that a delegate has no function body.

To make a delegate object point to a function, you create an object of that delegate type (MethodDelegate, in this case) and instantiate it with the method you want to point to, like this:

static void Main(string[] args)
        {
            int num1 = 5;
            int num2 = 3;
            MethodDelegate method = new MethodDelegate(Add);

Alternatively, you can also assign the function name to it directly, like this:

MethodDelegate method = Add;

This statement declares method to be a delegate that points to the Add() method. Hence instead of calling the Add() method directly, you can now call it using the method delegate:

//---Console.WriteLine(Add(num1, num2).ToString());---
            Console.WriteLine(method(num1, num2).ToString());

The beauty of delegates is that you can make the delegate call whatever function it refers to, without knowing exactly which function it is calling until runtime. Any function can be pointed by the delegate, as long as the function's signature matches the delegate's.

For example, the following statements check the value of the Operation variable before deciding which method the method delegate to point to:

char Operation = 'A';
            MethodDelegate method = null;
            switch (Operation)
            {
                case 'A': method = new MethodDelegate(Add);
                    break;
                case 'S': method = new MethodDelegate(Subtract);
                    break;
            }
            if (method != null)
                Console.WriteLine(method(num1, num2).ToString());

You can also pass a delegate to a method as a parameter, as the following example shows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Delegates
{
    class Program
    {
        delegate int MethodDelegate(int num1, int num2);

        static void PerformMathOps(MethodDelegate method, int num1, int num2)
        {
            Console.WriteLine(method(num1, num2).ToString());
        }

        static void Main(string[] args)
        {
            int num1 = 5;
            int num2 = 3;
            char Operation = 'A';

            MethodDelegate method = null;
            switch (Operation)
            {
                case 'A': method = new MethodDelegate(Add);
                    break;
                case 'S': method = new MethodDelegate(Subtract);
                    break;
            }
            if (method != null)
                PerformMathOps(method, num1, num2);

            Console.ReadLine();
        }

        static int Add(int num1, int num2)
        {
            return (num1 + num2);
        }

        static int Subtract(int num1, int num2)
        {
            return (num1 - num2);
        }
    }
}

In this example, the PerformMathOps() function takes in three arguments — a delegate of type MethodDelegate and two integer values. Which method to invoke is determined by the Operation variable. Once the delegate is assigned to point to a method (Add() or Subtract()), it is passed to the PerformMathOps() method.

Delegates Chaining (Multicast Delegates)

In the previous section, a delegate pointed to a single function. In fact, you can make a delegate point to multiple functions. This is known as delegates chaining. Delegates that point to multiple functions are known as multicast delegates.

Consider the following example:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Delegates
{
    class Program
    {
        delegate void MethodsDelegate();

        static void Main(string[] args)
        {
            MethodsDelegate methods = Method1;
            methods += Method2;
            methods += Method3;

            //---call the delegated method(s)---
            methods();
            Console.ReadLine();
        }

        static private void Method1()
        {
            Console.WriteLine("Method 1");
        }

        static private void Method2()
        {
            Console.WriteLine("Method 2");
        }

        static private void Method3()
        {
            Console.WriteLine("Method 3");
        }
    }
}

This program three methods: Method1(), Method2(), and Method3(). The methods delegate is first assigned to point to Method1(). The next two statements add Method2() and Method3() to the delegate by using the += operator:

MethodsDelegate methods = Method1;
            methods += Method2;
            methods += Method3;

When the methods delegate variable is called, the following output results:

Method 1
Method 2
Method 3

The output shows that the three methods are called in succession, in the order they were added.

What happens when your methods each return a value and you call them using a multicast delegate? Here's an example in which the three methods each return an integer value:

class Program
    {
        delegate int MethodsDelegate(ref int num1, ref int num2);

        static void Main(string[] args)
        {
            int num1 = 0, num2 = 0;
            MethodsDelegate methods = Method1;
            methods += Method2;
            methods += Method3;

            //---call the delegated method(s)---
            Console.WriteLine(methods(ref num1, ref num2));
            Console.WriteLine("num1: {0} num2: {1}", num1, num2);
            Console.ReadLine();
        }

        static private int Method1(ref int num1, ref int num2)
        {
            Console.WriteLine("Method 1");
            num1 = 1;
            num2 = 1;
            return 1;
        }

        static private int Method2(ref int num1, ref int num2)
        {
            Console.WriteLine("Method 2");
            num1 = 2;
            num2 = 2;
            return 2;
        }

        static private int Method3(ref int num1, ref int num2)
{
            Console.WriteLine("Method 3");
            num1 = 3;
            num2 = 3;
            return 3;
        }
    }

When the methods delegate is called, Method1(), Method2(), and Method3() are called in succession. However, only the last method (Method3()) returns a value back to the Main() function, as the output shows:

Method 1
Method 2
Method 3
3
num1: 3 num2: 3

If one of the methods pointed to by a delegate causes an exception, no results are returned. The following modifications to the preceding program shows that Method2() throws an exception and is caught by the Try-Catch block:

class Program
    {
        delegate int MethodsDelegate(ref int num1, ref int num2);
        static void Main(string[] args)
        {
            int num1 = 0, num2 = 0;
            MethodsDelegate methods = Method1;
            methods += Method2;
            methods += Method3;

            try
            {
                //---call the delegated method(s)---
                Console.WriteLine(methods(ref num1, ref num2));
                Console.WriteLine("num1: {0} num2: {1}", num1, num2);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            Console.WriteLine("num1: {0} num2: {1}", num1, num2);
            Console.ReadLine();
        }

        static private int Method1(ref int num1, ref int num2)
        {
            Console.WriteLine("Method 1");
            num1 = 1;
            num2 = 1;
return 1;
        }

        static private int Method2(ref int num1, ref int num2)
        {
            throw new Exception();
            Console.WriteLine("Method 2");
            num1 = 2;
            num2 = 2;
            return 2;
        }

        static private int Method3(ref int num1, ref int num2)
        {
            Console.WriteLine("Method 3");
            num1 = 3;
            num2 = 3;
            return 3;
        }
    }

The following output shows that num1 and num2 retain the values set by the last method that was successfully invoked by the delegate:

Method 1
Exception of type 'System.Exception' was thrown.
num1: 1 num2: 1

Just as you use the += operator to add a method to a delegate, you use the ––= operator to remove a method from a delegate:

static void Main(string[] args)
        {
            int num1 = 0, num2 = 0;
            MethodsDelegate methods = Method1;
            methods += Method2;
            methods += Method3;
            //...
            //...
            //---removes Method3---
            methods -= Method3;

Implementing Callbacks Using Delegates

One of the useful things you can do with delegates is to implement callbacks. Callbacks are methods that you pass into a function that will be called when the function finishes execution. For example, you have a function that performs a series of mathematical operations. When you call the function, you also pass it a callback method so that when the function is done with its calculation, the callback method is called to notify you of the calculation result.

Following is an example of how to implement callbacks using delegates:

class Program
    {
        delegate void callbackDelegate(string Message);

        static void Main(string[] args)
        {
            callbackDelegate result = ResultCallback;
            AddTwoNumbers(5, 3, result);

            Console.ReadLine();
        }

        static private void AddTwoNumbers(
            int num1, int num2, callbackDelegate callback)
        {
            int result = num1 + num2;
            callback("The result is: " + result.ToString());
        }

        static private void ResultCallback(string Message)
        {
            Console.WriteLine(Message);
        }
    }

First, you declare two methods:

  • AddTwoNumbers() — Takes in two integer arguments and a delegate of type callbackDelegate

  • ResultCallback() — Takes in a string argument and displays the string in the console window

Then you declare a delegate type:

delegate void callbackDelegate(string Message);

Before you call the AddTwoNumbers() function, you create a delegate of type callbackDelegate and assign it to point to the ResultCallback() method. The AddTwoNumbers() function is then called with two integer arguments and the result callback delegate:

callbackDelegate result = ResultCallback;
            AddTwoNumbers(5, 3, result);

In the AddTwoNumbers() function, when the calculation is done, you invoke the callback delegate and pass to it a string:

static private void AddTwoNumbers(
            int num1, int num2, callbackDelegate callback)
        {
            int result = num1 + num2;
            callback("The result is: " + result.ToString());
        }

The callback delegate calls the ResultCallback() function, which prints the result to the console. The output is:

The result is: 8

Asynchronous Callbacks

Callbacks are most useful if they are asynchronous. The callback illustrated in the previous example is synchronous, that is, the functions are called sequentially. If the AddTwoNumbers() function takes a long time to execute, all the statements after it will block. Figure 7-1 shows the flow of execution when the callback is synchronous.

Figure 7-1

Figure 7.1. Figure 7-1

A better way to organize the program is to call the AddTwoNumbers() method asynchronously, as shown in Figure 7-2. Calling a function asynchronously allows the main program to continue executing without waiting for the function to return.

Figure 7-2

Figure 7.2. Figure 7-2

In this asynchronous model, when the AddTwoNumbers() function is called, the statement(s) after it can continue to execute. When the function finishes execution, it calls the ResultCallback() function.

Here's the rewrite of the previous program, using an asynchronous callback:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Messaging;

namespace Delegates
{
    class Program
    {
        //---delegate to the AddTwoNumbers() method---
        delegate int MethodDelegate(int num1, int num2);

        static void Main(string[] args)
{
            //---assign the delegate to point to AddTwoNumbers()---
            MethodDelegate del = AddTwoNumbers;

            //---creates a AsyncCallback delegate---
            AsyncCallback callback = new AsyncCallback(ResultCallback);

            //---invoke the method asychronously---
            Console.WriteLine("Invoking the method asynchronously...");
            IAsyncResult result = del.BeginInvoke(5, 3, callback, null);
            Console.WriteLine("Continuing with the execution...");

            Console.ReadLine();
        }

        //---method to add two numbers---
        static private int AddTwoNumbers(int num1, int num2)
        {
            //---simulate long execution---
            System.Threading.Thread.Sleep(5000);
            return num1 + num2;
        }

        static private void ResultCallback(IAsyncResult ar)
        {
            MethodDelegate del =
                (MethodDelegate)((AsyncResult)ar).AsyncDelegate;
            //---get the result---
            int result = del.EndInvoke(ar);
            Console.WriteLine("Result of addition is: " + result);
        }
    }
}

First, you define a delegate type so that you can point to the AddTwoNumbers() method:

delegate int MethodDelegate(int num1, int num2);

Then create a delegate, and assign it to point to the AddTwoNumbers() method:

//---assign the delegate to point to AddTwoNumbers()---
            MethodDelegate del = AddTwoNumbers;

Next, define a delegate of type AsyncCallback:

//---creates a AsyncCallback delegate---
            AsyncCallback callback = new AsyncCallback(ResultCallback);

The AsyncCallback is a delegate that references a method to be called when an asynchronous operation completes. Here, you set it to point to ResultCallback (which you will define later).

To call the AddTwoNumbers() methods asynchronously, you use the BeginInvoke() method of the del delegate, passing it two integer values (needed by the AddTwoNumbers() method), as well as a delegate to call back when the method finishes executing:

//---invoke the method asychronously---
            Console.WriteLine("Invoking the method asynchronously...");
            IAsyncResult result = del.BeginInvoke(5, 3, callback, null);
            Console.WriteLine("Continuing with the execution...");

The BeginInvoke() method calls the delegate asynchronously, and the next statement continues execution after the async delegate is called. This method returns a variable of type IAsyncResult to represent the status of an asynchronous operation.

To obtain the result of the calculation, you define the ResultCallback() method, which takes in an argument of type IAsyncResult:

static private void ResultCallback(IAsyncResult ar)
        {
            MethodDelegate del =
                (MethodDelegate)((AsyncResult)ar).AsyncDelegate;
            //---get the result---
            int result = del.EndInvoke(ar);
            Console.WriteLine("Result of addition is: " + result);
        }

Within the ResultCallback() method, you first obtain the delegate to the AddTwoNumbers() method by using the AsyncDelegate property, which returns the delegate on which the asynchronous call was invoked. You then obtain the result of the asynchronous call by using the EndInvoke() method, passing it the IAsyncResult variable (ar).

Finally, to demonstrate the asynchronous calling of the AddTwoNumbers() method, you can insert a Sleep() statement to delay the execution (simulating long execution):

static private int AddTwoNumbers(int num1, int num2)
        {
            //---simulate long execution---
            System.Threading.Thread.Sleep(5000);
            return num1 + num2;
        }

Figure 7-3 shows the output of this program.

Figure 7-3

Figure 7.3. Figure 7-3

When using asynchronous callbacks, you can make your program much more responsive by executing different parts of the program in different threads.

Chapter 10 discusses more about threading.

Anonymous Methods and Lambda Expressions

Beginning with C# 2.0, you can use a feature known as anonymous methods to define a delegate. An anonymous method is an "inline" statement or expression that can be used as a delegate parameter. To see how it works, take a look at the following example:

class Program
    {
        delegate void MethodsDelegate(string Message);

        static void Main(string[] args)
        {
            MethodsDelegate method = Method1;

            //---call the delegated method---
            method("Using delegate.");
            Console.ReadLine();
        }

        static private void Method1(string Message)
        {
            Console.WriteLine(Message);
        }
    }

Instead of defining a separate method and then using a delegate variable to point to it, you can shorten the code using an anonymous method:

class Program
    {
        delegate void MethodsDelegate(string Message);

        static void Main(string[] args)
        {
            MethodsDelegate method = delegate(string Message)
            {
                Console.WriteLine(Message);
            };

            //---call the delegated method---
            method("Using anonymous method.");

            Console.ReadLine();
        }
    }

In this expression, the method delegate is an anonymous method:

MethodsDelegate method = delegate(string Message)
            {
                Console.WriteLine(Message);
            };

Anonymous methods eliminate the need to define a separate method when using delegates. This is useful if your delegated method contains a few simple statements and is not used by other code because you reduce the coding overhead in instantiating delegates by not having to create a separate method.

In C# 3.0, anonymous methods can be further shortened using a new feature known as lambda expressions. Lambda expressions are a new feature in .NET 3.5 that provides a more concise, functional syntax for writing anonymous methods.

The preceding code using anonymous methods can be rewritten using a lambda expression:

class Program
    {
        delegate void MethodsDelegate(string Message);

        static void Main(string[] args)
        {
            MethodsDelegate method = (Message) => { Console.WriteLine(Message); };

            //---call the delegated method---
            method("Using Lambda Expression.");

            Console.ReadLine();
        }
    }

Lambda expressions are discussed in more detail in Chapter 14.

Events

One of the most important techniques in computer science that made today's graphical user interface operating systems (such as Windows, Mac OS X, Linux, and so on) possible is event-driven programming. Event-driven programming lets the OS react appropriately to the different clicks made by the user. A typical Windows application has various widgets such as buttons, radio buttons, and checkboxes that can raise events when, say, a user clicks them. The programmer simply needs to write the code to handle that particular event. The nice thing about events is that you do not need to know when these events will be raised — you simply need to provide the implementation for the event handlers that will handle the events and the OS will take care of invoking the necessary event handlers appropriately.

In .NET, events are implemented using delegates. An object that has events is known as a publisher. Objects that subscribe to events (in other words, handle events) are known as subscribers. When an object exposes events, it defines a delegate so that whichever object wants to handle this event will have to provide a function for this delegate. This delegate is known as an event, and the function that handles this delegate is known as an event handler. Events are part and parcel of every Windows application. For example, using Visual Studio 2008 you can create a Windows application containing a Button control (see Figure 7-4).

Figure 7-4

Figure 7.4. Figure 7-4

When you double-click the Button control, an event handler is automatically added for you:

public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {

        }
    }

But how does your application know which event handler is for which event? Turns out that Visual Studio 2008 automatically wires up the event handlers in the code-behind of the form (FormName.Designer.cs; see Figure 7-5) located in a function called InitializeComponent():

this.button1.Location = new System.Drawing.Point(12, 12);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(75, 23);
            this.button1.TabIndex = 0;
            this.button1.Text = "button1";
            this.button1.UseVisualStyleBackColor = true;
            this.button1.Click += new System.EventHandler(this.button1_Click);
Figure 7-5

Figure 7.5. Figure 7-5

Notice that the way you wire up an event handler to handle the Click event is similar to how you assign a method name to a delegate.

Alternatively, you can manually create the event handler for the Click event of the Button control. In the Form() constructor, type += after the Click event and press the Tab key. Visual Studio 2008 automatically completes the statement (see Figure 7-6).

Figure 7-6

Figure 7.6. Figure 7-6

Press the Tab key one more time, and Visual Studio 2008 inserts the stub of the event handler for you (see Figure 7-7).

Figure 7-7

Figure 7.7. Figure 7-7

The completed code looks like this:

public Form1()
        {
            InitializeComponent();
            this.button1.Click += new EventHandler(button1_Click);
        }

        void button1_Click(object sender, EventArgs e)
        {

        }

Notice that Click is the event and the event handler must match the signature required by the event (in this case, the event handler for the Click event must have two parameter — object and EventArgs). By convention, event handlers in the .NET Framework return void and have two parameters. The first is the source of the event (that is, the object that raises this event), and the second is an object derived from EventArgs. The EventArgs parameter allows data to be passed from an event to the event handler. The EventArgs class is discussed further later in this chapter.

Using the new lambda expressions in C# 3.0, the preceding event handler can also be written like this:

public Form1()
        {
            InitializeComponent();
            this.button1.Click += (object sender, EventArgs e) =>
            {
                MessageBox.Show("Button clicked!");
            };
        }

Handling Events

Let's take a look at how to handle events using a couple of simple examples. The Timer class (located in the System.Timers namespace) is a class that generates a series of recurring events at regular intervals. You usually use the Timer class to perform some background tasks, such as updating a ProgressBar control when downloading some files from a server, or displaying the current time.

The Timer class has one important event that you need to handle — Elapsed. The Elapsed event is fired every time a set time interval has elapsed.

The following program shows how you can use the Timer class to display the current time in the console window:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Messaging;
using System.Timers;
namespace Events
{
    class Program
    {
        static void Main(string[] args)
        {
            Timer t = new Timer(1000);
            t.Elapsed += new ElapsedEventHandler(t_Elapsed);
            t.Start();
            Console.ReadLine();
        }

        static void t_Elapsed(object sender, ElapsedEventArgs e)
        {
            Console.SetCursorPosition(0, 0);
            Console.WriteLine(DateTime.Now);
        }
    }
}

First, you instantiate a Timer class by passing it a value. The value is the time interval (in milliseconds) between the Timer class's firing (raising) of its Elapsed event. You next wire the Elapsed event with the event handler t_Elapsed, which displays the current time in the console window. The Start() method of the Timer class activates the Timer object so that it can start to fire the Elapsed event. Because the event is fired every second, the console is essentially updating the time every second (see Figure 7-8).

Figure 7-8

Figure 7.8. Figure 7-8

Another useful class that is available in the .NET Framework class library is the FileSystemWatcher class (located in the System.IO namespace). It watches the file system for changes and enables you to monitor these changes by raising events. For example, you can use the FileSystemWatcher class to monitor your hard drive for changes such as when a file/directory is deleted, is created, or has its contents changed.

To see how the FileSystemWatcher class works, consider the following program:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Messaging;
using System.IO;

namespace Events
{
    class Program
    {
        static void Main(string[] args)
        {
            FileSystemWatcher fileWatcher = new FileSystemWatcher()
            {
                Path = @"c:",
                Filter = "*.txt"
            };

            //---wire up the event handlers---
            fileWatcher.Deleted += new FileSystemEventHandler(fileWatcher_Deleted);
            fileWatcher.Renamed += new RenamedEventHandler(fileWatcher_Renamed);
            fileWatcher.Changed += new FileSystemEventHandler(fileWatcher_Changed);
            fileWatcher.Created += new FileSystemEventHandler(fileWatcher_Created);

            //---begin watching---
            fileWatcher.EnableRaisingEvents = true;
            Console.ReadLine();
        }

        static void fileWatcher_Created(object sender, FileSystemEventArgs e)
{
            Console.WriteLine("File created: " + e.FullPath);
        }

        static void fileWatcher_Changed(object sender, FileSystemEventArgs e)
        {
            Console.WriteLine("File changed: " + e.FullPath);
        }

        static void fileWatcher_Renamed(object sender, RenamedEventArgs e)
        {
            Console.WriteLine("File renamed: " + e.FullPath);
        }

        static void fileWatcher_Deleted(object sender, FileSystemEventArgs e)
        {
            Console.WriteLine("File deleted: " + e.FullPath);
        }
    }
}

You first create an instance of the FileSystemWatcher class by initializing its Path and Filter properties:

FileSystemWatcher fileWatcher = new FileSystemWatcher()
            {
                Path = @"c:",
                Filter = "*.txt"
            };

Here, you are monitoring the C: drive and all its files ending with the .txt extension.

You then wire all the events with their respective event handlers:

//---wire up the event handlers---
            fileWatcher.Deleted += new FileSystemEventHandler(fileWatcher_Deleted);
            fileWatcher.Renamed += new RenamedEventHandler(fileWatcher_Renamed);
            fileWatcher.Changed += new FileSystemEventHandler(fileWatcher_Changed);
            fileWatcher.Created += new FileSystemEventHandler(fileWatcher_Created);

These statements handle four events:

  • Deleted — Fires when a file is deleted

  • Renamed — Fires when a file is renamed

  • Changed — Fires when a file's content is changed

  • Created — Fires when a file is created

Finally, you define the event handlers for the four events:

static void fileWatcher_Created(object sender, FileSystemEventArgs e)
        {
            Console.WriteLine("File created: " + e.FullPath);
        }

        static void fileWatcher_Changed(object sender, FileSystemEventArgs e)
        {
            Console.WriteLine("File changed: " + e.FullPath);
        }

        static void fileWatcher_Renamed(object sender, RenamedEventArgs e)
        {
            Console.WriteLine("File renamed: " + e.FullPath);
        }

        static void fileWatcher_Deleted(object sender, FileSystemEventArgs e)
        {
            Console.WriteLine("File deleted: " + e.FullPath);
        }

To test the program, you can create a new text file in C: drive, make some changes to its content, rename it, and then delete it. The output window will look like Figure 7-9.

Figure 7-9

Figure 7.9. Figure 7-9

Implementing Events

So far you have been subscribing to events by writing event handlers. Now you will implement events in your own class. For this example, you create a class called AlarmClock. AlarmClock allows you to set a particular date and time so that you can be notified (through an event) when the time is up. For this purpose, you use the Timer class.

First, define the AlarmClock class as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;

class AlarmClock
{

}

Declare a Timer variable and define the AlarmTime property to allow users of this class to set a date and time:

class AlarmClock
{
    Timer t;
    public DateTime AlarmTime { get; set; }
}

Next, define the Start() method so that users can start the monitoring by turning on the Timer object:

class AlarmClock
{
    //...
    public void Start()
    {
        t.Start();
    }
}

Next, define a public event member in the AlarmClock class:

public event EventHandler TimesUp;

The EventHandler is a predefined delegate, and this statement defines TimesUp as an event for your class.

Define a protected virtual method in the AlarmClock class that will be used internally by your class to raise the TimesUp event:

protected virtual void onTimesUp(EventArgs e)
    {
        if (TimesUp != null)
            TimesUp(this, e);
    }

The EventArgs class is the base class for classes that contain event data. This class does not pass any data back to an event handler.

The next section explains how you can create another class that derives from this EventArgs base class to pass back information to an event handler.

Define the constructor for the AlarmClock class so that the Timer object (t) will fire its Elapsed event every 100 milliseconds. In addition, wire the Elapsed event with an event handler. The event handler will check the current time against the time set by the user of the class. If the time equals or exceeds the user's set time, the event handler calls the onTimesUp() method that you defined in the previous step:

public AlarmClock()
    {
        t = new Timer(100);
        t.Elapsed += new ElapsedEventHandler(t_Elapsed);
    }

    void t_Elapsed(object sender, ElapsedEventArgs e)
    {
        if (DateTime.Now >= this.AlarmTime)
        {
            onTimesUp(new EventArgs());
            t.Stop();
        }
    }

That's it! The entire AlarmClock class is:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Timers;

class AlarmClock
{
    Timer t;
    public DateTime AlarmTime { get; set; }

    public void Start()
    {
        t.Start();
    }

    public AlarmClock()
    {
        t = new Timer(100);
        t.Elapsed += new ElapsedEventHandler(t_Elapsed);
    }

    void t_Elapsed(object sender, ElapsedEventArgs e)
    {
        if (DateTime.Now >= this.AlarmTime)
        {
            onTimesUp(new EventArgs());
t.Stop();
        }
    }

    public event EventHandler TimesUp;
    protected virtual void onTimesUp(EventArgs e)
    {
        if (TimesUp != null)
            TimesUp(this, e);
    }
}

To use the AlarmClock class, you first create an instance of the AlarmClock class and then set the time for the alarm by using the AlarmTime property. You then wire the TimesUp event with an event handler so that you can print a message when the set time is up:

class Program
    {
        static void Main(string[] args)
        {
            AlarmClock c = new AlarmClock()
            {
                //---alarm to sound off at 16 May 08, 9.50am---
                AlarmTime = new DateTime(2008, 5, 16, 09, 50, 0, 0),
            };
            c.Start();
            c.TimesUp += new EventHandler(c_TimesUp);

            Console.ReadLine();
        }

        static void c_TimesUp(object sender, EventArgs e)
        {
            Console.WriteLine("Times up!");
        }
    }

Difference between Events and Delegates

Events are implemented using delegates, so what is the difference between an event and a delegate? The difference is that for an event you cannot directly assign a delegate to it using the = operator; you must use the += operator.

To understand the difference, consider the following class definitions — Class1 and Class2:

namespace DelegatesVsEvents
{
    class Program
    {
        static void Main(string[] args)
        {
        }
    }

    class Class1
    {
        public delegate void Class1Delegate();
        public Class1Delegate del;
    }

    class Class2
    {
        public delegate void Class2Delegate();
        public event Class2Delegate evt;    }
}

In this code, Class1 exposes a public delegate del, of type Class1Delegate. Class2 is similar to Class1, except that it exposes an event evt, of type Class2Delegate. del and evt each expect a delegate, with the exception that evt is prefixed with the event keyword.

To use Class1, you create an instance of Class1 and then assign a delegate to the del delegate using the "=" operator:

static void Main(string[] args)
        {
            //---create a delegate---
            Class1.Class1Delegate d1 =
                new Class1.Class1Delegate(DoSomething);

            Class1 c1 = new Class1();

            //---assign a delegate to del of c1---
            c1.del = new Class1.Class1Delegate(d1);
        }

        static private void DoSomething()
        {
            //...
        }

To use Class2, you create an instance of Class2 and then assign a delegate to the evt event using the += operator:

static void Main(string[] args)
        {
            //...

            //---create a delegate---
            Class2.Class2Delegate e2 =
                new Class2.Class2Delegate(DoSomething);

            Class2 c2 = new Class2();

            //---assign a delegate to evt of c2---
            c2.evt += new Class2.Class2Delegate(d1);
        }

If you try to use the = operator to assign a delegate to the evt event, you will get a compilation error:

c2.evt = new Class2.Class2Delegate(d1); //---error---

This important restriction of event is important because defining a delegate as an event will ensure that if multiple clients are subscribed to an event, another client will not be able to set the delegate to null (or simply set it to another delegate). If the client succeeds in doing so, all the other delegates set by other client will be lost. Hence, a delegate defined as an event can only be set with the += operator.

Passing State Information to an Event Handler

In the preceding program, you simply raise an event in the AlarmClock class; there is no passing of information from the class back to the event handler. To pass information from an event back to an event handler, you need to implement your own class that derives from the EventArgs base class.

In this section, you modify the previous program so that when the set time is up, the event passes a message back to the event handler. The message is set when you instantiate the AlarmClock class.

First, define the AlarmClockEventArgs class that will allow the event to pass back a string to the event handler. This class must derive from the EventArgs base class:

public class AlarmClockEventArgs : EventArgs
{
    public AlarmClockEventArgs(string Message)
    {
        this.Message = Message;
    }
    public string Message { get; set; }
}

Next, define a delegate called AlarmClockEventHandler with the following signature:

public delegate void AlarmClockEventHandler(object sender, AlarmClockEventArgs e);

Replace the original TimesUp event statement with the following statement, which uses the AlarmClockEventHandler class:

//---public event EventHandler TimesUp;---
    public event AlarmClockEventHandler TimesUp;

Add a Message property to the class so that users of this class can set a message that will be returned by the event when the time is up:

public string Message { get; set; }

Modify the onTimesUp virtual method by changing its parameter type to the new AlarmClockEventArgs class:

protected virtual void onTimesUp(AlarmClockEventArgs e)
    {
        if (TimesUp != null)
            TimesUp(this, e);
    }

Finally, modify the t_Elapsed event handler so that when you now call the onTimesUp() method, you pass in an instance of the AlarmClockEventArgs class containing the message you want to pass back to the event handler:

void t_Elapsed(object sender, ElapsedEventArgs e)
    {
        if (DateTime.Now >= this.AlarmTime)
        {
            onTimesUp(new AlarmClockEventArgs(this.Message));
            t.Stop();
        }
    }

Here's the complete program:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;

public class AlarmClockEventArgs : EventArgs
{
public AlarmClockEventArgs(string Message)
    {
        this.Message = Message;
    }
    public string Message { get; set; }
}

public delegate void AlarmClockEventHandler(object sender, AlarmClockEventArgs e);

class AlarmClock
{
    Timer t;

    public event AlarmClockEventHandler TimesUp;

    protected virtual void onTimesUp(AlarmClockEventArgs e)
    {
        if (TimesUp != null)
            TimesUp(this, e);
    }

    public DateTime AlarmTime { get; set; }
    public string Message { get; set; }

    public AlarmClock()
    {
        t = new Timer(100);
        t.Elapsed += new ElapsedEventHandler(t_Elapsed);
    }

    public void Start()
    {
        t.Start();
    }

    void t_Elapsed(object sender, ElapsedEventArgs e)
    {
        if (DateTime.Now >= this.AlarmTime)
        {
            onTimesUp(new AlarmClockEventArgs(this.Message));
            t.Stop();
        }
    }
}

With the modified AlarmClock class, your program will now look like this:

namespace Events
{
    class Program
    {
        static void c_TimesUp(object sender, AlarmClockEventArgs e)
        {
            Console.WriteLine(DateTime.Now.ToShortTimeString() + ": " + e.Message);
        }

        static void Main(string[] args)
        {
            AlarmClock c = new AlarmClock()
            {
                //---alarm to sound off at 16 May 08, 9.50am---
                AlarmTime = new DateTime(2008, 5, 16, 09, 50, 0, 0),
                Message = "Meeting with customer."
            };
            c.TimesUp += new AlarmClockEventHandler(c_TimesUp);
            c.Start();
            Console.ReadLine();
        }
    }
}

Figure 7-10 shows the output when the AlarmClock fires the TimesUp event.

Figure 7-10

Figure 7.10. Figure 7-10

Summary

This chapter discussed what delegates are and how you can use them to invoke other functions, as well as how you can use delegates to implement callbacks so that your application is more efficient and responsive. One direct application of delegates is events, which make GUI operating systems such as Windows possible. One important difference between delegates and events is that you cannot assign a delegate to an event by using the = operator.

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

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