Chapter 16. Events

Events Are Like Delegates

The preceding chapter covered delegates. Many aspects of events are similar to those of delegates. In fact, an event is like a simpler delegate that is specialized for a particular use. Figure 16-1 illustrates that, like a delegate, an event has methods registered with it, and invokes those methods when it is invoked.

The following are some important terms related to events:

  • Raising an event: The term for invoking or firing an event. When an event is raised, all the methods registered with it are invoked—in order.

  • Publisher: A class or struct that makes an event available to other classes or structs for their use.

  • Subscriber: A class or struct that registers methods with a publisher.

  • Event handler: A method that is registered with an event. It can be declared in the same class or struct as the event, or in a different class or struct.

Publishers and subscribers

Figure 16-1. Publishers and subscribers

An Event Has a Private Delegate

There's good reason for the similarities in the behaviors of delegates and events. An event contains a private delegate, as illustrated in Figure 16-2. The important things to know about an event's private delegate are the following:

  • An event gives structured access to its privately controlled delegate.

  • Unlike the many operations available with a delegate, with an event, you can only add, remove, and invoke event handlers.

  • When an event is raised, it invokes the delegate, which sequentially calls the methods in the invocation list.

Notice in Figure 16-2 that only the += and -= operators are sticking out to the left of the event. This is because they are the only operations allowed on an event.

An event as an encapsulated delegate

Figure 16-2. An event as an encapsulated delegate

Figure 16-3 illustrates the runtime view of a publisher class with an event called Elapsed. ClassA and ClassB, on the right, each have an event handler registered with Elapsed. Inside the event you can see the delegate referencing the two event handlers. Besides the event, the publisher also contains the code that raises the event.

Structure and terminology of a class with a timer event

Figure 16-3. Structure and terminology of a class with a timer event

Overview of Source Code Components

There are five components of code that need to be in place to use events. I will cover each of them in the following sections, and they are illustrated in Figure 16-4. These components are the following:

  • Delegate type declaration: The event and the event handlers must have a common signature and return type, which is described by the delegate type declaration.

  • Event handler declarations: These are the declarations in the subscriber classes of the methods (event handlers) to be executed when the event is raised. These do not have to be separate methods. They can be anonymous methods or lambda expressions.

  • Event declaration: This is the declaration in the publisher class of the event that holds and invokes the event handlers.

  • Event registration: This is the code that connects the event handlers to the event.

  • Code that raises the event: This is the code in the publisher that calls the event, causing it to invoke its event handlers.

The five source code components of using an event

Figure 16-4. The five source code components of using an event

Declaring an Event

The publisher must provide the event and the code to raise the event.

Creating an event is simple—it requires only a delegate type and a name. The syntax for an event declaration is shown in the following code, which declares an event called Elapsed. Notice the following about event Elapsed:

  • It is declared inside a class called MyTimerClass.

  • It accepts event handlers with the return type and signature matching the delegate type EventHandler.

  • It is declared public so that other classes and structs can register event handlers with it.

Declaring an Event

You can declare more than one event in a declaration statement by using a commaseparated list. For example, the following statement declares three events.

Declaring an Event

You can also make events static, by including the static keyword, as shown in the following declaration:

Declaring an Event

An Event Is a Member

A common error is to think of an event as a type, which it is not. An event is a member, and there are several important ramifications to this:

  • Because a member is not a type, you do not use an object-creation expression (a new expression) to create its object.

  • Because an event is a member

    • It must be declared in a class or struct, with the other members.

    • You cannot declare an event in a block of executable code.

  • An event member is implicitly and automatically initialized to null with the other members.

The Delegate Type and EventHandler

An event declaration requires the name of a delegate type. You can either declare one or use one that already exists. If you declare a delegate type, it must specify the signature and return type of the methods that will be stored by the event.

A better idea is to use the predefined delegate type used by the .NET BCL and designated as the standard for use with events. You are strongly encouraged to use it. It is the EventHandler delegate, and its declaration is shown in the following code. Delegate EventHandler is covered in more detail later in this chapter.

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

Raising an Event

The event member itself just holds the event handlers that need to be invoked. Nothing happens with them unless the event is raised. You need to make sure there is code to do just that, at the appropriate times.

For example, the following code raises event Elapsed. Notice the following about the code:

  • Before raising the event, it is compared to null, to see whether it contains any event handlers. If the event is null, it is empty.

  • Raising the event itself is like invoking a function.

    • Use the name of the event, followed by the parameter list enclosed in parentheses.

    • The parameter list must match the delegate type of the event.

Raising an Event

Putting together the event declaration and the code to raise the event gives the following class declaration for the publisher. The code contains two members: the event, and a method called OnOneSecond, which raises the event.

Raising an Event

For now, I'll let method OnOneSecond be somehow, mysteriously, called once every second. Later in the chapter I'll show you how to make this happen. But for now, remember these important points:

  • The publisher class has an event as a member.

  • The class contains the code to raise the event.

Subscribing to an Event

To add an event handler to an event, the handler must have the same return type and signature as the event's delegate.

  • Use the += operator to add an event handler to an event, as shown in the following code.

  • The method can be any of the following:

    • An instance method

    • A static method

    • An anonymous method

    • A lambda expression

For example, the following code adds three methods to event Elapsed. The first is an instance method using the method form. The second is a static method using the method form. The third is an instance method using the delegate form.

Subscribing to an Event

Just as with delegates, you can use anonymous methods and lambda expressions to add event handlers. For example, the following code first uses a lambda expression and then an anonymous method.

mc.Elapsed += (source, args) =>                        // Lambda expression
      {
         Console.WriteLine("Lambda expression.");
      };


   mc.Elapsed += delegate(object source, EventArgs args)  // Anonymous method
      {
         Console.WriteLine("Anonymous method.");
      };

The following program uses the MyTimerClass class declared in the previous section. The code performs the following:

  • It registers two event handlers from two different class instances.

  • After registering the event handlers, it sleeps for 2 seconds. During that time, the timer class raises the event two times, and both event handlers are executed each time.

public class MyTimerClass { ... }

   class ClassA
   {
      public void TimerHandlerA(object obj, EventArgs e)      // Event handler
      {
         Console.WriteLine("Class A handler called");
      }
   }

   class ClassB
   {
      public static void TimerHandlerB(object obj, EventArgs e)   // Static
      {
         Console.WriteLine("Class B handler called");
      }
   }

   class Program
   {
      static void Main( )
      {
         ClassA ca = new ClassA();                // Create the class object.
         MyTimerClass mc = new MyTimerClass();    // Create the timer object.

         mc.Elapsed += ca.TimerHandlerA;          // Add handler A -- instance.
         mc.Elapsed += ClassB.TimerHandlerB;      // Add handler B -- static.

         Thread.Sleep(2250);
      }
   }

When supplied with the code for MyTimerClass, this code produces the following output:

Class A handler called
Class B handler called
Class A handler called
Class B handler called

Removing Event Handlers

You can remove an event handler from an event by using the -= operator, as shown here:

mc.Elapsed -= ca.TimerHandlerA;         // Remove handler A.

For example, the following code removes the event handler for ClassB after the first two times the event is raised, and then lets the program run for another 2 seconds.

...
   mc.Elapsed += ca.TimerHandlerA;         // Add instance handler A.
   mc.Elapsed += ClassB.TimerHandlerB;     // Add static handler B.

   Thread.Sleep(2250);                     // Sleep more than 2 seconds.

   mc.Elapsed -= ClassB.TimerHandlerB;     // Remove static handler B.
   Console.WriteLine("Class B event handler removed");

   Thread.Sleep(2250);                     // Sleep more than 2 seconds.

This code produces the following output. The first four lines are the result of both handlers being called twice, in the first 2 seconds. After the handler for ClassB is removed, only the handler for the instance of ClassA is called, during the last 2 seconds.

Class A handler called
Class B handler called
Class A handler called
Class B handler called
Class B event handler removed
Class A handler called
Class A handler called

Standard Event Usage

GUI programming is event driven, which means that while the program is running, it can be interrupted at any time by events such as button clicks, key presses, or system timers. When this happens, the program needs to handle the event and then continue on its course.

Clearly, this asynchronous handling of program events is the perfect situation to use C# events. Windows GUI programming uses events so extensively that there is a standard .NET Framework pattern for using them, which you are strongly encouraged to follow.

The foundation of the standard pattern for event usage is the EventHandler delegate type, which is declared in the System namespace. The declaration of the EventHandler delegate type is shown in the following code.

  • The first parameter is meant to hold a reference to the object that raised the event. It is of type object and can, therefore, match any instance of any type.

  • The second parameter is meant to hold state information of whatever type is appropriate for the application.

  • The return type is void.

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

Using the EventArgs Class

The second parameter in the EventHandler delegate type is an object of class EventArgs, which is declared in the System namespace. You might be tempted to think that, since the second parameter is meant for passing data, an EventArgs class object would be able to store data of some sort. You would be wrong.

  • The EventArgs class is designed to carry no data. It is used for event handlers that do not need to pass data—and is generally ignored by them.

  • If you want to pass data, you must declare a class derived from EventArgs, with the appropriate fields to hold the data you want to pass.

Even though the EventArgs class does not actually pass data, it is an important part of the pattern of using the EventHandler delegate. Class object and class EventArgs are the base classes for whatever actual types are used as the parameters. This allows EventHandler to provide a signature that is the lowest common denominator for all events and event handlers, allowing them to have exactly two parameters, rather than having different signatures for each case.

Passing Data by Extending EventArgs

To pass data in the second parameter of your event handler and adhere to the standard conventions, you need to declare a custom class derived from EventArgs that can store the data you need passed. The name of the class should end in EventArgs. For example, the following code declares a custom class that can store a string in a field called Message.

Passing Data by Extending EventArgs

Using the Custom Delegate

Now that you have a custom class for passing data in the second parameter of your event handlers, you need a delegate type that uses the new custom class. There are two ways you can do this:

  • The first way is to use a nongeneric delegate. To do this, do the following:

    • Create a new custom delegate using your custom class type, as shown in the following code.

    • Use the new delegate name throughout the four other sections of the event code.

      Using the Custom Delegate
  • The second way was introduced with C# 2.0, and uses the generic delegate EventHandler<>. C# generics are covered in Chapter 19. To use the generic delegate, do the following, as shown in the following code:

    • Place the name of the custom class between the angle brackets.

    • Use the entire string wherever you would have used the name of your custom delegate type. For example, this is what the event declaration would look like:

      Using the Custom Delegate

Use the custom class and the custom delegate, either nongeneric or generic, in the other four sections of code dealing with the event.

For example, the following code updates the MyTimerClass code to use a custom EventArgs class called MyTCEventArgs and the generic EventHandler<> delegate.

Using the Custom Delegate

This code produces the following output:

Class A Message:  Message from OnOneSecond
Class A Message:  Message from OnOneSecond
Class A Message:  Message from OnOneSecond

The MyTimerClass Code

Now that you've seen all five components of code that need to be implemented to use an event, I can show you the full MyTimerClass class that the code has been using.

Most things about the class have been pretty clear—it has an event called Elapsed that can be subscribed to, and a method called OnOneSecond that is called every second and raises the event. The one question remaining about it is, "What causes OnOneSecond to be called every second?"

The answer is that OnOneSecond is itself an event handler that is subscribed to an event in a class called Timer, in the System.Timers namespace. The event in Timer is raised every 1,000 milliseconds and calls event handler OnOneSecond, which in turn raises event Elapsed in class MyTimerClass. Figure 16-5 shows the structure of the code.

The code structure of MyTimerClass

Figure 16-5. The code structure of MyTimerClass

The Timer class is a useful tool, so I'll mention a little more about it. First, it has a public event called Elapsed. If that sounds familiar, it's because I named the event in MyTimerClass after it. The names have no other connection than that. I could have named the event anything.

One of the properties of Timer is Interval, which is of type double, and specifies the number of milliseconds between raising the event. The other property the code uses is Enabled, which is of type bool, and starts and stops the timer.

The actual code is the following. The only things I haven't shown previously are the private timer field, called MyPrivateTimer, and the constructor for the class. The constructor does the work of setting up the internal timer and attaching it to event handler OnOneSecond.

public class MyTimerClass
   {
      public event EventHandler Elapsed;

      private void OnOneSecond(object obj, EventArgs e)
      {
         if (Elapsed != null)
            Elapsed(obj, e);
      }

      //------------
      private System.Timers.Timer MyPrivateTimer;    // Private timer

      public MyTimerClass()                          // Constructor
      {
         MyPrivateTimer = new System.Timers.Timer(); // Create the private timer.


         // The following statement sets our OnOneSecond method above as an event
         // handler to the Elapsed event of class Timer. It is completely
         // unrelated to our event Elapsed, declared above.
         MyPrivateTimer.Elapsed += OnOneSecond;      // Attach our event handler.


         // Property Interval is of type double, and specifies the number of
         // milliseconds between when its event is raised.
         MyPrivateTimer.Interval = 1000;             // 1 second interval.


         // Property Enabled is of type bool, and turns the timer on and off.
         MyPrivateTimer.Enabled = true;               // Start the timer.
      }
   }

Event Accessors

The last topic to cover in this chapter is event accessors. I mentioned earlier that the += and -= operators were the only operators allowed for an event. These operators have the well-defined behavior that you have seen so far in this chapter.

You can, however, change these operators' behavior and have the event perform whatever custom code you like when they are used. You can do this by defining event accessors for the event.

  • There are two accessors: add and remove.

  • The declaration of an event with accessors looks similar to the declaration of a property.

The following example shows the form of an event declaration with accessors. Both accessors have an implicit value parameter called value, which takes a reference to either an instance method or a static method.

public event EventHandler Elapsed
   {
      add
      {
         ...                            // Code to implement the =+ operator
      }
      remove
      {
         ...                            // Code to implement the -= operator
      }
   }

When event accessors are declared, the event does not contain an embedded delegate object. You must implement your own storage mechanism for storing and removing the methods registered with the event.

The event accessors act as void methods, meaning that they cannot use return statements that return a value.

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

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