One common requirement in many programs is that when a particular program event occurs, other parts of the program need to be notified that the event has occurred.
One pattern for satisfying this requirement is called the publisher/subscriber pattern. In this pattern, a class, called the publisher, defines a set of events that other parts of the program might be interested in. Other classes can then “sign up” to be notified by the publisher when these events occur. These subscriber classes “sign up” for notification by supplying a method to the publisher. When the event occurs, the publisher “raises the event,” and all the methods submitted by the subscribers are executed.
The methods supplied by the subscribers are called callback methods, because the publisher “calls the subscribers back” by executing their methods. They are also called event handlers, because they are the code that is called to handle the event. Figure 14-1 illustrates the process, showing the publisher with an event and three subscribers to the event.
The following are some important terms related to events:
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. There's good reason for the similarities in the behaviors of delegates and events. An event contains a private delegate, as illustrated in Figure 14-2.
The important things to know about an event's private delegate are the following:
Notice in Figure 14-2 that only the +=
and -=
operators are sticking out to the left of the event box. This is because they are the only operations (apart from invoking the event itself) allowed on an event.
Figure 14-3 illustrates a program with a class called Incrementer
, which performs a count of some sort.
Incrementer
defines an event called CountedADozen
, which it raises every time it counts another dozen items.Dozens
and SomeOtherClass
each have an event handler registered with the CountedADozen
event.There are five pieces of code that need to be in place to use events. These are illustrated in Figure 14-4. I'll cover each of them in the following sections. These pieces of code are the following:
public
event, it is said to have published the event.The publisher must provide the event object. 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 CountedADozen
. Notice the following about event CountedADozen
:
public
so that other classes and structs can register event handlers with it.new
expression) with an event. class Incrementer
{ Keyword Name of event
↓ ↓
public event EventHandler CountedADozen;
↑
Delegate type
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:
public event EventHandler MyEvent1, MyEvent2, OtherEvent;
↑
Three events
You can also make events static by including the static
keyword, as shown in the following declaration:
public static event EventHandler CountedADozen;
↑
Keyword
A common error is to think of an event as a type—which it's not. Like a method, or a property, an event is a member of a class or a struct, and there are several important ramifications to this:
null
with the other members.To declare an event, you must supply 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 registered by the event.
The BCL declares a delegate called EventHandler
specifically for use with system events. I'll describe the EventHandler
delegate later in this chapter.
Subscribers add event handlers to the event. For an event handler to be added 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 CountedADozen
. The first is an instance method. The second is a static method. The third is an instance method, using the delegate form.
Class instance Instance method
↓ ↓
incrementer.CountedADozen += IncrementDozensCount; // Method reference form
incrementer.CountedADozen += ClassB.CounterHandlerB; // Method reference form
↑ ↑
Event member Static method
mc.CountedADozen += new EventHandler(cc.CounterHandlerC); // 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.
// Lambda expression
incrementer.CountedADozen += () => DozensCount++;
// Anonymous method
incrementer.CountedADozen += delegate { DozensCount++; };
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 CountedADozen
. Notice the following about the code:
null
to see whether it contains any event handlers. If the event is null
, it is empty and cannot be executed. if (CountedADozen != null) // Make sure there are methods to execute.
CountedADozen (source, args); // Raise the event.
↑ ↑
Event name Parameter list
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 DoCount
, which raises the event when appropriate.
class Incrementer
{
public event EventHandler CountedADozen; // Declare the event.
void DoCount(object source, EventArgs args)
{
for( int i=1; i < 100; i++ )
if( i % 12 == 0 )
if (CountedADozen != null) // Make sure there are methods to execute.
CountedADozen(source, args);
} ↑
Raise the event.
}
The code in Figure 14-5 shows the whole program, with publisher class Incrementer
and subscriber class Dozens
. The things to note about the code are the following:
Dozens
subscribes to the event, supplying method IncrementDozensCount
as its event handler.DoCount
in class Incrementer
, event CountedADozen
is raised each time the method increments another 12 times.The code in Figure 14-5 produces the following output:
Number of dozens = 8
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 for using C# events. Windows GUI programming uses events so extensively that there is a standard .NET Framework pattern for using them. The foundation of the standard pattern for event usage is the EventHandler
delegate type, which is declared in the System
namespace. The following line of code shows the declaration of the EventHandler
delegate type. The things to notice about the declaration are the following:
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. These parameters, of types object
and EventArgs
, are the base classes for whatever actual types are used as the parameters. This allows the EventHandler
delegate to provide a signature that is the lowest common denominator for all events and event handlers, allowing all events to have exactly two parameters, rather than having different signatures for each case.
If we modify the Incrementer
program to use the EventHandler
delegate, we have the program shown in Figure 14-6. Notice the following about the code:
Handler
has been removed since the event uses the system-defined EventHandler
delegate.object
and EventArgs
. In the case of event handler IncrementDozensCount
, the method just ignores the formal parameters.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
:
Custom class name Base class
↓ ↓
public class IncrementerEventArgs : EventArgs
{
public int IterationCount { get; set; } // Stores an integer
}
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. To obtain this, use the generic version of delegate EventHandler<>
. Chapter 17 covers C# generics in detail, so for now you'll just have to watch. To use the generic delegate, do the following, as shown in the subsequent code:
event
declaration would look like: Generic delegate using custom class
↓
public event EventHandler<IncrementerEventArgs> CountedADozen;
↑
Event name
Use the custom class and the custom delegate in the other four sections of code dealing with the event. For example, the following code updates the Incrementer
code to use the custom EventArgs
class called IncrementerEventArgs
and the generic EventHandler<IncrementerEventArgs>
delegate.
public class IncrementerEventArgs : EventArgs // Custom class derived from EventArgs
{
public int IterationCount { get; set; } // Stores an integer
}
class Incrementer Generic delegate using custom class
{ ↓
public event EventHandler<IncrementerEventArgs> CountedADozen;
public void DoCount()
Object of custom class
{
↓
IncrementerEventArgs args = new IncrementerEventArgs();
for ( int i=1; i < 100; i++ )
if ( i % 12 == 0 && CountedADozen != null )
{
args.IterationCount = i;
CountedADozen( this, args );
} ↑
} Pass parameters when raising the event
}
class Dozens
{
public int DozensCount { get; private set; }
public Dozens( Incrementer incrementer )
{
DozensCount = 0;
incrementer.CountedADozen += IncrementDozensCount;
}
void IncrementDozensCount( object source, IncrementerEventArgs e )
{
Console.WriteLine( "Incremented at iteration: {0} in {1}",
e.IterationCount, source.ToString() );
DozensCount++;
}
}
class Program
{
static void Main()
{
Incrementer incrementer = new Incrementer();
Dozens dozensCounter = new Dozens( incrementer );
incrementer.DoCount();
Console.WriteLine( "Number of dozens = {0}",
dozensCounter.DozensCount );
}
}
This program produces the following output, which displays the iteration when it was called and the fully qualified class name of the source object. I'll cover fully qualified class names in Chapter 21.
Incremented at iteration: 12 in Counter.Incrementer
Incremented at iteration: 24 in Counter.Incrementer
Incremented at iteration: 36 in Counter.Incrementer
Incremented at iteration: 48 in Counter.Incrementer
Incremented at iteration: 60 in Counter.Incrementer
Incremented at iteration: 72 in Counter.Incrementer
Incremented at iteration: 84 in Counter.Incrementer
Incremented at iteration: 96 in Counter.Incrementer
Number of dozens = 8
When you're done with an event handler, you can remove it from the event. You remove an event handler from an event by using the -=
operator, as shown in the following line of code:
p.SimpleEvent -= s.MethodB;; // Remove handler MethodB.
For example, the following code adds two handlers to event SimpleEvent
, and then raises the event. Each of the handlers is called and prints out a line of text. The MethodB
handler is then removed from the event, and when the event is raised again, only the MethodB
handler prints out a line.
class Publisher
{
public event EventHandler SimpleEvent;
public void RaiseTheEvent() { SimpleEvent( this, null ); }
}
class Subscriber
{
public void MethodA( object o, EventArgs e ) { Console.WriteLine( "AAA" ); }
public void MethodB( object o, EventArgs e ) { Console.WriteLine( "BBB" ); }
}
class Program
{
static void Main( )
{
Publisher p = new Publisher();
Subscriber s = new Subscriber();
p.SimpleEvent += s.MethodA;
p.SimpleEvent += s.MethodB;
p.RaiseTheEvent();
Console.WriteLine( "
Remove MethodB" );
p.SimpleEvent -= s.MethodB;
p.RaiseTheEvent();
}
}
This code produces the following output:
AAA
BBB
Remove MethodB
AAA
If a handler is registered more than once with an event, then when you issue the command to remove the handler, only the last instance of that handler is removed from the list.
The last topic to cover in this chapter is event accessors. I mentioned earlier that the +=
and -=
operators are the only operators allowed for an event. These operators have the well-defined behavior that you've seen so far in this chapter.
It is possible, however, to change these operators' behavior and have the event perform whatever custom code you like when they are used. This is an advanced topic, however, so I'll just mention it here, without going into too much detail.
To change the operation of these operators, you must define 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 CountedADozen
{
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.
18.218.171.212