Events and Event Handling - C# Fundamentals - Sams Teach Yourself C# 5.0 in 24 Hours (2013)

Sams Teach Yourself C# 5.0 in 24 Hours (2013)

Part I: C# Fundamentals

Hour 7. Events and Event Handling


What You’ll Learn in This Hour

Understanding events

Subscribing and unsubscribing

Publishing an event

Raising an event


C# is inherently an imperative programming language, which enables you to describe how to accomplish tasks using procedures (methods). Procedural programming defines the exact statements that will be executed and the order in which they will be executed. This type of programming is most often found in command-line or noninteractive programs because of the generally limited amount of user interaction.

In contrast, event-driven programming is a programming style in which the program flow is determined by events. Events are simply anything of interest, such as user actions (mouse clicks or key presses) or messages from other programs or parts of the same program. In C#, the class that raises, or sends, the event is the publisher, and the classes that receive, or handle, the event are subscribers. Although you can use events for a variety of reasons, they are most commonly used to signal user actions in a graphical user interface, such as keyboard-related events (keys pressed) and mouse-related events (mouse movement, button clicks, and so on).

In this hour, you learn the basics of event-driven programming, including how to define your own events, initiate and respond to events, and send data through events.

Understanding Events

Events follow a publish/subscribe model where the publisher determines when an event is raised. The subscribers determine the action taken in response to that event. Events can have multiple subscribers, in which case the event handlers are invoked synchronously when the event is raised. If an event has no subscribers, it is never actually raised. Subscribers can handle multiple events from multiple publishers.


Note: Delegates

In traditional programming terms, an event is a form of a callback, which enables a method to be passed as an argument to other methods. In C#, these callbacks are referred to as delegates. Event handlers are actually nothing more than methods invoked through delegates.

You can think of delegates as being similar to C or C++ function pointers or Delphi closures and are a type-safe and secure means of writing a callback. Delegates run under the caller’s security permissions, not the declarer’s permissions.

A delegate type simply defines a method signature and any method with a compatible signature can be associated with a delegate. One key difference between delegate signatures and regular method signatures is that the return type is included in the signature and the use of the params modifier in the parameter list is allowed.


Subscribing and Unsubscribing

When you are interested in responding to an event published by another class, you subscribe to that event by defining an event handler method whose signature matches the event delegate signature. You then use the addition assignment operator (+=) to attach the event handler to the event.Listing 7.1 shows how you would subscribe to an event based on the ElapsedEventHandler delegate.

Listing 7.1. Subscribing to an Event


var timer = new System.Timers.Timer(1000);
timer.Elapsed += TimerElapsedHandler;

private void TimerElapsedHandler(object sender, System.Timers.ElapsedEventArgs e)
{
MessageBox.Show("The timer has expired.");
}


The first parameter of the event handler method should always be an object parameter named sender that represents the object that raised the event. The second parameter represents the data passed to the event handler method. This should always be an EventArgs type or a type derived fromEventArgs named e. The event handler method should always have a void return type.

The method name can be anything you want, but it is best to be consistent and use a name that is the combination of the name of the object providing the event, the name of the event being handled, and the word Handler.

Although subscribing to events in this manner is common for many classes, Visual Studio makes it easy to subscribe to events, especially those published by any of the user interface controls.

These steps show how to subscribe to a button’s Click event in a Windows Presentation Foundation (WPF) application:

1. Make sure the button is selected and click the Events tab on the top of the Properties window in Design view, as shown in Figure 7.1. If you don’t see the Properties window, right-click the form or control to which you are going to handle an event and select Properties.

Image

Figure 7.1. The Properties window.

2. Double-click the event you will be handling. This causes Visual Studio to create an empty event handler method in your code, shown in Figure 7.2.

Image

Figure 7.2. The generated event handler method.

Visual Studio also attaches the event handler for you, as shown in Figure 7.3. For WPF applications, this is done in the XML markup, or XAML, which describes the control.

Image

Figure 7.3. Attaching the event handler in XAML.

If you create a Windows Forms-based application, the process is similar but instead of attaching the event handler in the XAML, Visual Studio would attach the event handler in the InitializeComponent method of your form, as shown in Figure 7.4.

Image

Figure 7.4. Attaching the event handler in code.

When you double-click the event, Visual Studio uses a default naming convention. If you want to use a different event handler method name, simply type the name in the text area next to the event, and press the Enter key to cause Visual Studio to create the event handler method. If you double-click the control itself rather than the event, Visual Studio creates an event handler method for the default event of the control.


Try It Yourself: Subscribing to Events

To subscribe to events published by a user interface control in a Windows Forms application and a WPF application, follow these steps:

1. Open the Hour7 solution in Visual Studio.

2. Open Form1.cs in the EventsWinForms project by double-clicking the file.

3. In the Design view for Form1, select the button and add an event handler for the Click event.

4. In the generated event handler method, add the following statement:

MessageBox.Show("You pressed the button.");

5. Run the application by pressing Ctrl+F5. When you click the button, you should see the following dialog box appear, as shown in Figure 7.5.

Image

Figure 7.5. MessageBox from the event handler.

6. Open MainWindow.xaml in the EventsWpf project by double-clicking the file.

7. In the Design view for MainWindow, select the button and add an event handler for the Click event. In the generated event handler method, add the same statement you added in step 4.

8. Run the application by right-clicking the project in the Solution Explorer window and selecting the Start New Instance option from the Debug menu, as shown in Figure 7.6.

Image

Figure 7.6. Solution Explorer context menu.

9. When you click the button, you should see the following dialog box appear, as shown in Figure 7.7.

Image

Figure 7.7. MessageBox from the event handler.


When you no longer want your event handler invoked when the event is raised, you must unsubscribe from the event. You should also unsubscribe from events before you dispose of the subscriber object. If you don’t unsubscribe from the events, the publishing object continues to hold a reference to the delegate that represents the subscriber’s event handler, which prevents the garbage collector from deallocating your subscriber object.

To unsubscribe from an event, you either remove the attribute from the XAML markup or use the subtraction assignment operator (-=), as shown here:

timer.Elapsed -= TimerElapsedHandler;


Note: Anonymous Methods

In the previous examples, you attached a named method as the event handler delegate. You can also use the addition assignment operator to attach an anonymous method to the event. An anonymous method provides a way to write an unnamed inline statement block that can be executed in a delegate invocation.

The code shown in Listing 7.1 is shown here using an anonymous method instead of a named delegate:

var timer = new Timer(1000);
timer.Elapsed += delegate(object sender, ElapsedEventArgs e)
{
MessageBox.Show("The timer has expired.");
}

Although using an anonymous method for an event handler provides a lot of convenience, it does not provide an easy way to unsubscribe from the event.


Publishing an Event

Events can be published by both classes and structs (although they are more commonly found in classes) using a simple event declaration. Events can be based on any valid delegate type; however, the standard practice is to base your events on the EventHandler and EventHandler<T> delegates. These are delegate types predefined in the .NET Framework specifically for defining events.

The first decision you need to make when defining your own events is whether you need to send custom data with your event. The .NET Framework provides the EventArgs class, which the predefined event delegate types support. If you need to send custom data with your event, you should create a new class that derives from EventArgs. If you don’t need to send custom data, you can use the EventArgs type directly, but cannot change it later without breaking compatibility. As a result, you should always create a new class that derives from EventArgs, even if it is initially empty, to provide the flexibility later on to add data.

Listing 7.2 shows an example of a custom EventArgs derived class.

Listing 7.2. A Custom EventArgs Derived Class


public class CustomEventArgs : System.EventArgs
{
private object data;

public CustomEventArgs(object data)
{
this.data = data;
}

public object Data
{
get
{
return data;
}
}
}


The most common way of declaring your event is using a fieldlike syntax. If you have no custom EventArgs class, you would use the EventHandler delegate type, shown in Listing 7.3.

Listing 7.3. A Simple Event Declaration


public class Contact
{
public event EventHandler AddressChanged;
}


If you do have a custom EventArgs class, you would use the generic EventHandler<T> delegate, substituting your own EventArgs class for the T, as shown in Listing 7.5.


Go To

HOUR 12, “UNDERSTANDING GENERICS,” for more information on generics.



Note: Using Event Properties

Although the fieldlike event definition is the most common, it might not always be the most efficient, particularly for classes with a large number of events. Consider a class with a large number of events. It is reasonable that only a few events have subscribers. Using the field declaration style, you create one field per event, which results in a lot of unnecessary overhead.

To solve this problem, C# also enables defining events with a property-like syntax, as shown in Listing 7.4.

Listing 7.4. Event Declaration Using Event Properties


1. public class Contact
2. {
3. private EventHandlerList events = new EventHandlerList();
4. private static readonly object addressChangedEventKey = new object();
5.
6. public event EventHandler AddressChanged
7. {
8. add
9. {
10. events.AddHandler(addressChangedEventKey, value);
11. }
12. remove
13. {
14. events.RemoveHandler(addressChangedEventKey, value);
15. }
16. }
17. }


Line 3 declares an EventHandlerList specifically designed to contain a list of event delegates. This enables you to use a single variable that contains an entry for every event that has a subscriber. Next, line 4 declares a static read-only object variable namedaddressChangedEventKey that represents the key used for the event in the EventHandlerList. Finally, lines 6 through 16 declare the actual event property.

This syntax should be familiar to you because it is almost the same syntax for defining a property. The difference is that rather than get and set accessors, you have add and remove accessors. The add accessor simply adds the input delegate instance to the list, whereas theremove accessor removes it. Both of the accessors use the predefined key for the event property to add and remove instances from the list.


Now that you understand the basics of publishing an event, a convenient and consistent way to describe when the event occurs is to categorize them as pre-events and post-events.

Post-events are the most common type of event and occur after the state of the object has changed. Pre-events, also called cancelable events, occur before the state of the object changes and provide the capability to cancel the event. These events use the CancelEventArgs class to store event data. The CancelEventArgs class simply adds a Cancel property your code can read and write. If you create or own cancelable events, you should derive your own custom event data class from the CancelEventArgs class.

Raising an Event

Defining an event isn’t of much use if no mechanism is in place to initiate that event. Event initiation is called raising, or firing, an event and follows a standard pattern. By following a pattern, it becomes easier to work with events because the structure is well defined and consistent.

Listing 7.5 builds on the example in Listing 7.3 and shows the complete event handler mechanism.

Listing 7.5. The Complete Event Handler


1. public class Contact
2. {
3. public event EventHandler<AddressChangedEventArgs> AddressChanged;
4.
5. private string address;
6.
7. protected virtual void OnAddressChanged(AddressChangedEventArgs e)
8. {
9. EventHandler<AddressChangedEventArgs> handler = AddressChanged;
10. if (handler != null)
11. {
12. handler(this, e);
13. }
14. }
15.
16. public string Address
17. {
18. get { return this.address; }
19. set
20. {
21. this.address = value;
22. AddressChangedEventArgs args =
23. new AddressChangedEventArgs(this.address);
24. OnAddressChanged(args);
25. }
26. }
27. }


Line 3 declares the event, using the EventHandler<T> delegate. Lines 7 through 14 declare a protected virtual method used to raise the event. By making this method protected and virtual, any derived classes have the capability to handle the event by overriding the method rather than subscribing to the event. This is a more natural and convenient mechanism for derived classes. Finally, lines 22 through 24 declare a new EventArgs class and raise the event. If the event did not have custom data, you could have used the EventArgs.Empty field to represent an empty EventArgs.


Note: Raising an Event When Using Event “Properties”

If you use the property-like syntax, the method used to actually raise the event needs to be a bit different to retrieve the event handler from the handler list, as shown here:

protected virtual void OnAddressChanged(AddressChangedEventArgs e)
{
var handler = events[addressChangedEventKey] as
EventHandler<AddressChangedEventArgs>;

if (handler != null)
{
handler(this, e);
}
}


By convention, the name of the event raiser method starts with “On” followed by the name of the event. For nonstatic events on unsealed classes, this method should be declared as a protected virtual method. For static events, nonstatic events on sealed classes, or events on structs, the method should be public. This method should always have a void return type and take a single parameter, named e, which should be typed to the appropriate EventArgs class.

The content of this method also follows a standard pattern, which makes a temporary copy of the event (line 9) to avoid the possibility of a race condition occurring if the last subscriber unsubscribes immediately after the null check (line 10) and before the event is raised (line 12).


Go To

HOUR 24, “UNDERSTANDING THREADS, CONCURRENCY, AND PARALLELISM,” for more information on race conditions.



Caution: Multithreading and Events

This pattern only prevents one possible type of race condition, whereby the event becomes null after the check and is only relevant if the code is multithreaded. There are complexities that must be safeguarded against when writing multithreaded events, such as ensuring that any necessary state is still present in a thread-safe manner before executing code that relies on that state.



Try It Yourself: Publishing and Raising Events

By following these steps, you explore how to publish and raise events:

1. Open the PublishAndRaise project in Visual Studio.

2. Add a class named AddressChangedEventArgs. This class should follow the same pattern as shown in Listing 7.2.

3. Add a class named Contact that looks like the one shown in Listing 7.5.

4. Run the application by pressing Ctrl+F5. The output should look like Figure 7.8. Make sure to set the PublishAndRaise project as the startup project.

Image

Figure 7.8. Results of subscribing to an event.


Summary

In this hour, you learned how C# enables you to create highly interactive applications by raising and responding to events. You also learned that events are not just about user interaction through a graphical user interface but that events also provide a rich and sophisticated notification system that your classes can use.

Through the Visual Studio editor, you have seen how easy it is to create your own event handlers for responding to events initiated through the user interface.

Q&A

Q. What is a delegate?

A. A delegate is a type-safe and secure way of writing a callback, similar to a C++ function pointer or Delphi closure. Using a delegate allows you to encapsulate a reference to a method. The code that calls the referenced method does not need to know at compile time which method will be invoked. Delegates run under the caller’s security permissions, not the declarer’s permissions.

Q. What is an event?

A. An event is any external stimulus to a program, such as user actions (mouse clicks or key presses), messages from other programs, or parts of the same programs.

Q. What is the EventArgs class?

A. The EventArgs class stores data for an event. Although it can be used directly, it is best to derive a new class for your event, even if it is initially empty.

Q. What are the two types of event?

A. The two types of event are pre-events and post-events. Pre-events are cancelable events raised before the state of the object changes. Post-events are raised after the state of the object has changed.

Workshop

Quiz

1. What is the most common way to declare an event and what are the drawbacks of using it?

2. Using a property-like syntax to declare an event requires what two accessor members?

3. The standard pattern for raising an event requires a method with what accessibility for nonstatic events on an unsealed class?

4. Looking at the OnAddressChanged method declared in Listing 7.5, why is a copy of the event delegate made?

Answers

1. The most common way to define an event is to use the fieldlike syntax. For classes with a large number of events, particularly when it is reasonable that only a small number of those events will have subscribers, this syntax creates one field per event and results in a lot of unnecessary overhead.

2. The property-like syntax for declaring an event uses an add and remove accessor. The add accessor simply adds the input delegate to the list, whereas the remove accessor removes it.

3. For nonstatic events on unsealed classes, the method should be declared as a protected virtual method, which enables any derived classes the capability to handle the event using an override.

4. A temporary copy of the delegate is made to avoid one possible race condition where the event is reset to null, causing a runtime error when the event is invoked.

Exercises

1. Extend the application you wrote in the “Publishing and Raising Events” Try It Yourself exercise to add a cancelable AddressChanging event. If the event is canceled, do not actually change the value of the address variable. Modify the AddressChangedEventArgs class to contain the old and new value of the address, which will be printed by the event handler in the subscriber class