Overview of Source Code Components
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:
Figure 16-1. Publishers and subscribers
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:
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.
Figure 16-2. An event has 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 has 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.
Figure 16-3. Structure and terminology of a class with a timer event
Five components of code need to be in place to use events. I'll cover each of them in the following sections, and they are illustrated in Figure 16-4. These components are the following:
Figure 16-4. The five source code components of using an event
The publisher must provide the event and often provides 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
:
MyTimerClass
.EventHandler
.You can declare more than one event in a declaration statement by using a comma-separated list. For example, the following statement declares three events:
You can also make events static, by including the static
keyword, as shown in the following declaration:
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:
new
expression) to create its object.null
with the other members.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 EventHandler
delegate, which is a predefined delegate type used by the .NET BCL and designated as the standard for use with events. You are strongly encouraged to use it. The following code shows what EventHandler
's declaration looks like in the BCL. The EventHandler
delegate is covered in more detail later in this chapter.
public delegate void EventHandler( object sender, EventArgs e );
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:
null
, to see whether it contains any event handlers. If the event is null
, it is empty.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.
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:
To add an event handler to an event, the handler must have the same return type and signature as the event's delegate.
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.
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 uses 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:
public class MyTimerClass { ... }
class ClassA
{
public void TimerHandlerA(object source, EventArgs args) // Event handler
{
Console.WriteLine("Class A handler called");
}
}
class ClassB
{
public static void TimerHandlerB(object source, EventArgs args) // 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
When you're done with an event handler, you should remove it from the event, to allow the garbage collector to free up that memory. You 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 two 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 two seconds. After the handler for ClassB
is removed, only the handler for the instance of ClassA
is called, during the last two 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
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:
object
and can, therefore, match any instance of any type.void
. public delegate void EventHandler(object sender, EventArgs e);
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.
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.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.
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
:
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:
EventHandler<>
. Chapter 19 covers C# generics. To use the generic delegate, do the following, as shown in the code following:
event
declaration would look like: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.
class Program
{
static void Main()
{
ClassA ca = new ClassA();
MyTimerClass mc = new MyTimerClass();
mc.Elapsed += // Register handler.
new EventHandler<MyTCEventArgs> (ca.TimerHandlerA);
Thread.Sleep(3250);
}
}
This code produces the following output:
Class A Message: Message from OnOneSecond
Class A Message: Message from OnOneSecond
Class A Message: Message from OnOneSecond
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 I've created method OnOneSecond
and subscribed it as an event handler 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.
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 in MyTimerClass
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 source, EventArgs args)
{
if (Elapsed != null)
Elapsed(source, args);
}
//------------
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.
}
}
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've 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.
add
and remove
.The following example shows the form of an event declaration with accessors. Both accessors have an implicit value parameter called value
that 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.
3.145.61.170