Cancellable Events

by Scosby Sunday, June 5, 2011

Introduction

This post will introduce the user to cancellable events. This special kind of event can be cancelled based on one or more event handlers. This post will also explore the problems with the default behavior of executing multiple CancelEventHandler methods, and how the user can override the default behavior by controlling the execution of the registered event handlers.

Scenario

Assume a class exists which launches a rocket. This class should encapsulate the required functionality to abort the launch, ignite the rocket, and report lift off. The class defines two events: AbortLaunch and Launched. There is one public method named Launch that raises the AbortLaunch event, if not aborted it then ignites the rocket and raises the Launched event.

A Simple Example

The following code sample demonstrates how easy it is to create a cancellable event and evaluate the result. This post will utilize a console application, but the techniques can also be applied to other types of applications.

Code Sample – A Simple Rocket Class

     public class Rocket

    {

        //Events...

        public event System.ComponentModel.CancelEventHandler AbortLaunch;

        public event System.EventHandler Launched;

 

        //Event Methods...

        protected virtual bool OnAbortLaunch()

        {

            if (this.AbortLaunch != null)

            {

                System.ComponentModel.CancelEventArgs args = new CancelEventArgs();

 

                this.AbortLaunch(this, args);

 

                return args.Cancel;

            }

 

            return false;

        }

 

        //Launch Methods...

        public void Launch()

        {

            if (!this.OnAbortLaunch())

            {

                //3..2..1..ignition!

 

                if (this.Launched != null)

                {

                    this.Launched(this, EventArgs.Empty);

                }

            }

        }

    }
 

Code Sample – A single event handler

Calling the SimpleRocketLaunch method demonstrates how the Rocket class works by default. Try changing the someCondition variable in the rocket_AbortLaunch event handler to see how the behavior is different.

        private static void SimpleRocketLaunch()

        {

            Rocket rocket = new Rocket();

 

            rocket.AbortLaunch += new CancelEventHandler(rocket_AbortLaunch);

 

            rocket.Launched += new EventHandler(rocket_Launched);

 

            rocket.Launch();

 

            Console.WriteLine("Press any key to exit...");

 

            Console.ReadKey(true);

        }

 

        private static void rocket_Launched(object sender, EventArgs e)

        {

            Console.WriteLine("Rocket launched!");

        }

 

        private static void rocket_AbortLaunch(object sender, CancelEventArgs e)

        {

            bool someCondition = false;

 

            e.Cancel = someCondition;

        }

 

System.ComponentModel.CancelEventHandler

This class enables the user to create an event which can be cancelled. An instance of CancelEventArgs is passed to all the event handlers, which can set CancelEventArgs.Cancel to true if the event should be cancelled.

Class Definition:

public sealed class CancelEventHandler : MulticastDelegate

Combining Event Handlers

The user can add more than one event handler to an event. It is important to remember an event handler is always a delegate, while a delegate may or may not be an event handler! The user can combine multiple event handlers to provide a robust means for checking if the event should be cancelled. This is sometimes known as “chaining delegates” in the community. The CancelEventHandler type derives from MulticastDelegate and there is a recommended behavior for calling all of a MulticastDelegate’s event handlers, collectively they are named the invocation chain; as explained in the MSDN documentation:

When a multicast delegate is invoked, the delegates in the invocation list are called synchronously in the order in which they appear. If an error occurs during execution of the list then an exception is thrown.

Code Sample – Multiple Event Handlers

This code sample demonstrates the problem with raising the CancelEventHandler in the current implementation of the Rocket class. In this sample, multiple event handlers have been added, via expression lambdas, to the AbortLaunch method. The user should notice how the launch should be aborted in the 2nd handler. However, after running the code, the user should be surprised to see the rocket was launched!

        private static void ComplexRocketLaunch()

        {

            Rocket rocket = new Rocket();

 

            rocket.AbortLaunch += new CancelEventHandler(rocket_AbortLaunch);

 

            rocket.AbortLaunch +=

                (o, e) =>

                {

                    //CANCEL THE LAUNCH!!!

                    bool anotherCondition = true;

 

                    e.Cancel = anotherCondition;

                };

 

            rocket.AbortLaunch +=

                (o, e) =>

                {

                    bool yetAnotherCondition = false;

 

                    e.Cancel = yetAnotherCondition;

                };

 

            rocket.Launched += new EventHandler(rocket_Launched);

 

            rocket.Launch();

 

            Console.WriteLine("Press any key to exit...");

 

            Console.ReadKey(true);

        }

 Problem

The event passes the same CancelEventArgs instance to all delegates in the invocation list. While this solution works well for events that have only one registered event handler, it creates a problem once the user combines, or chains, multiple event handlers. If the user invokes the CancelEventHandler as shown above in the “Multiple Event Handlers” code sample, a problem can occur. Specifically, the last handler to set the CancelEventArgs.Cancel property is the only relevant handler. In other words, only the last event handler will determine if the event should be cancelled.

Solution

The user must invoke the event differently than other events. The user should retrieve a collection of the registered event handlers by calling the GetInvocationList method on the CancelEventHandler. This will allow the user to determine if the event was cancelled after each event handler.

Code Sample – A Robust Rocket Class


   public class Rocket 
  
{

        //Events...

        public event System.ComponentModel.CancelEventHandler AbortLaunch;

        public event System.EventHandler Launched;

 

        //Event Methods...

        protected virtual bool OnAbortLaunch()

        {

            if (this.AbortLaunch != null)

            {

                System.ComponentModel.CancelEventArgs args = new CancelEventArgs();

 

                foreach (System.ComponentModel.CancelEventHandler handler in this.AbortLaunch.GetInvocationList())

                {

                    if (args.Cancel)

                    {

                        break;

                    }

 

                    handler(this, args);

                }

 

                return args.Cancel;

            }

 

            return false;

        }

 

        //Launch Methods...

        public void Launch()

        {

            if (!this.OnAbortLaunch())

            {

                //3..2..1..ignition!

 

                if (this.Launched != null)

                {

                    this.Launched(this, EventArgs.Empty);

                }

            }

        }

    }

The user will notice how this Rocket class raises the AbortLaunch event differently in its implementation of the OnAbortLaunch method. By iterating over each handler from the invocation list, the user can inspect the result of the previous handler and determine if the next handler should even be called.

The result of this subtle change is a more natural behavior from the Rocket class that most users would expect.  After making these changes to the Rocket class, call the ComplexRocketLaunch method again and notice how the rocket is not launched!

Summary

This post introduced the user to the CancelEventHandler by presenting a rocket class capable of aborting a launch based on a registered event handler. The user was shown the problem with the default behavior of raising the CancelEventHandler event, and then shown how to fix the problem by explicitly invoking the registered event handlers by calling the GetInvocationList method.

For More Information

·         CancelEventHandler Delegate

·         MulticastDelegate

·         MulticastDelegate.GetInvocationList Method

Tags:

IT | Programming

blog comments powered by Disqus