Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

C# event fundamentals and exception handling in multicast delegates

0.00/5 (No votes)
2 Jun 2009 1  
How to declare and use C# events and handle exceptions from event handlers.

Introduction

This article introduces the basic concepts of .NET events and then shows how to handle exceptions thrown from event handlers/subscribers. The purpose or focus of this article is to show some basic concepts and exception handling.

Background

I thought of writing this article to show how to handle unhandled exceptions from event handlers/subscribers. Although it sounds easy at first, it is not straightforward unless you have in-depth knowledge about delegates. When I was searching for information on exceptions, I could not find any articles, so I thought of writing one myself.

Contents

  1. What is an Event?
  2. Creating and using Events
  3. Beyond basics
  4. Difference between delegates and events
  5. Events and Exception handling

Back in the days of C and C++, a function pointer was the only option to call a method asynchronously or call a method from another class. This technique, though powerful, was unsafe. Type safety was not guaranteed, and the publisher of the method had no control over when the method was called. Microsoft came up with a solution using delegates and events. A delegate provides type safety for the function pointer and the event provides control over when to call the remote method. As you will see, events and delegates are one and the same thing, but used differently.

What is an Event?

An Event can be defined as a function pointer. It notifies its subscriber of an incident. It means that it stores the pointer to a method which will be invoked when the "event" happens. As you can see from the definition, pointers are at the heart of the event system. So, the question is how is it type safe. After all, pointers are not considered a type safe way of calling a method.

The answer is Events in C# use delegates to point to a method. The type safety is guaranteed by the use of delegates. A delegate only allows storing a pointer to a method if the target method satisfies the method signature of the delegate. This will guarantee that the event will not call illegal methods when invoked. Events in .NET are based on the Publisher-Subscriber model.

Declaring and using Events

Let's look at how we can define an event for our application/class.

The event keyword lets you specify a delegate that will be called upon the occurrence of some "event" in your code. The following steps must be taken in order to create and use C# events:

  1. Create or identify a delegate. If you are defining your own event, you must also ensure that there is a delegate to use with the event keyword. If the event is predefined, in the .NET Framework for example, then consumers of the event need only know the name of the delegate.
  2. Create a class that contains:
    1. An event created from the delegate.
    2. (Optional) A method that verifies that an instance of the delegate declared with the event keyword exists. Otherwise, this logic must be placed in the code that fires the event.
    3. Methods that call the event. These methods can be overrides of some base class functionality.

    This class defines the event.

  3. Define one or more classes that connect methods to the event. Each of these classes will include:
    • Associate one or more methods, using the += and -= operators, with the event in the base class.
    • The definition of the method(s) that will be associated with the event.
  4. Use the event:
    • Create an object of the class that contains the event declaration.
    • Create an object of the class that contains the event definition, using the constructor that you defined.

The above steps are best understood by an example.

Our first step is to define or identify the delegate. As explained earlier, we need a delegate to point to a method. You can create your own delegate or use the delegates defined in the .NET Framework if it satisfies your need. For simplicity, we will use a pre-defined delegate. The EventHandler delegate is the most commonly used delegate in .NET, so we will use that. The signature of the method that can be stored in EventHandler is as follows:

void publisher_MyEvent(object sender, EventArgs e); 

The next step is to create a Publisher. It is a class which defines the event and fires the event. The class defines a method to fire the event and check the null reference for the delegate.

public class Publisher
{
    public event EventHandler MyEvent;
    protected virtual void OnMyEvent()
    {
        if(MyEvent != null)
        {
            MyEvent(this, new EventArgs());
        }
    }
    public void RaiseEvent()
    {
        OnMyEvent();
    }
}

In the next step, we will create a Subscriber which will listen to the event and implement a method which will be called when the event is fired.

public class Consumer
{
    Publisher publisher = new Publisher();
    public Consumer(Publisher pub)
    {
        publisher = pub;
        publisher.MyEvent += new EventHandler(publisher_MyEvent);
    }

    public void RaiseEvent()
    {
        publisher.RaiseEvent();
    }

    void publisher_MyEvent(object sender, EventArgs e)
    {
        Console.WriteLine("Event Handler Exexuted.");
    }
}

Note the syntax of attaching the event handler to the actual event. As you might recollect, .NET has multicast delegates, meaning it can store more than one reference to the methods in a single delegate. To accomplish that, we have used the += operator. You can use = as well, but it will remove all the previous references and just assign the last one. The whole purpose of the event system is to notify all the subscribers of an event, not just one. The class also defines an event handler which matches our delegate signature. publisher_MyEvent is that method. When the event is fired in the Publisher class, this method will be executed.

Finally, we need to use both the above classes to see the event system in action. You can create a console application to test it. Use the following Main method in your class:

public class Program
{
    public static void Main(string[] args)
    {
        Publisher pub = new Publisher();
        Consumer consumer = new Consumer(pub);
        consumer.RaiseEvent();
        Console.ReadLine("Press any key to exit");
    }
}

So far, you have learned how to declare, fire, and handle events. These are the basics that you need to know to define and use events in your application.

Beyond basics

Have you ever wondered what the "event" keyword does?

The "event" keyword is actually just a modifier for the delegate type. It encapsulates the delegate field and gives read only access to the subscribers. You must have noticed from the code we just wrote that the field MyEvent does not allow any operation other than assigning a handler. This is precisely what the "event" keyword is used for.

We can implement a pattern similar to events by just using the delegates in our class. To clarify, just remove the keyword event from the event declaration in the Publisher class and run the application, and you will not see a difference in the execution.

public class Publisher
{
    public EventHandler MyEvent;
    protected virtual void OnMyEvent()
    {
        if(MyEvent != null)
        {
            MyEvent(this, new EventArgs());
        }
    }
    public void RaiseEvent()
    {
        OnMyEvent();
    }
}

The problem with this approach is that the delegate MyEvent becomes a public field. The delegate can be executed by subscribers without any control of the publisher. This approach is not thread safe either. You can create a private delegate instance in the publisher and create methods to access the delegate to resolve the issue. The same resolution is implemented via event declaration.

The event declaration you saw earlier is just a short hand notation used in C#. The full declaration would look like this:

public class Publisher
{
    private EventHandler _myEvent;
    public event EventHandler MyEvent
    {
    add { _myEvent += value;}
    remove {_myEvent -= value;}
    }
    protected virtual void OnMyEvent()
    {
        if(_myEvent != null)
        {
            _myEvent(this, new EventArgs());
        }
    }
    public void RaiseEvent()
    {
        OnMyEvent();
    }
}

In the above code, the event looks like a property procedure with add and remove accessors instead of get and set. This shows us that the event keyword just facilitates the access to the delegate in the Publisher. This also shows us that events and delegates are similar objects. There are a couple of subtle differences though.

Difference between delegates and events

As we have seen earlier, events and delegates are similar things, containing method pointers. Following are the two main differences:

  • An interface cannot have delegates but can have event declaration.
  • Events can only be raised by the class which declares it, unlike a delegate.

The first point is a handy tool when you want to have a common delegate for all your classes implementing the interface. The second point gives full control of the delegate to the publisher, the subscriber cannot call the method without asking the publisher to do so. After all, events are supposed to be raised by the publisher, not by the subscriber.

Exception handling

Exception handling is a common task associated with any C# application, events are also part of that process. As you can see, the publisher does not have access to the code written in the event handler, and the publisher will have no control over the exceptions being thrown from the event handler. The responsibility of handling exceptions lies on the subscriber.

An event is nothing but an encapsulated multicast delegate. It can store references to more than one method or event handler. The methods will be executed in the same order as they are added to the event. So, if a handler throws an exception, then what happens? Does the rest of the handler execute or not? The answer is the rest of the methods will not execute, the exception propagates to a higher level. Unless it is handled at the top level, the application might crash.

Let's reproduce the above scenario with an example. By now we already have a Publisher, a Subscriber, and a Console application to test our code. Add one more Subscriber class and throw an exception from the event handler.

public class Consumer1
{
    Publisher publisher = new Publisher();
    public Consumer1(Publisher pub)
    {
        publisher = pub;
        publisher.MyEvent += new EventHandler(publisher_MyEvent);
    }

    public void RaiseEvent()
    {
        publisher.RaiseEvent();
    }

    void publisher_MyEvent(object sender, EventArgs e)
    {
        throw new NotImplementedException();
    }
}

Modify the Console application to instantiate the Consumer1 object.

public class Program
{
    public static void Main(string[] args)
    {
    Publisher pub = new Publisher();
    Consumer1 con = new Consumer1(pub);
            Consumer consumer = new Consumer(pub);
            pub.RaiseEvent();
            Console.ReadLine("Press any key to exit");
    }
}

Execute the application. You will notice that the application does not execute the event handler from the consumer object because of the exception thrown from the con1 object. So, what is the solution?

To resolve the issue, we need to run the registered event handlers one by one in a try-catch block. See the following class definition:

public class Publisher
{
    private EventHandler _myEvent;
    public event EventHandler MyEvent
    {
        add { _myEvent += value;}
        remove {_myEvent -= value;}
    }
    protected virtual void OnMyEvent()
    {
        if(_myEvent != null)
        {
               foreach(EventHandler handler in _myEvent.GetInvocationList())
               {
                    try
                    {
                            handler(this, new EventArgs());
                    }
                    catch(Exception e)
                    {
                        Console.WriteLine("Error in the handler {0}: {1}", 
                        handler.Method.Name, e.Message);
                    }
               }
        }
    }
    public void RaiseEvent()
    {
        OnMyEvent();
    }
}

If you run the code now, you will see the error from the first handler and the message from the second handler.

When the subscriber subscribes to an event with the use of the += operator, internally a delegate is created for that subscriber and added to the invocation list of the event. So, an event itself contains a list of delegates to invoke. This list can be obtained by calling the method GetInvocationList(). This method returns an array of delegates. You then need to execute the delegates one by one and handle any unhandled exceptions yourself.

Thread safety considerations

Is the above code thread safe? The answer is no. There are a couple of techniques you can utilise to make the Publisher thread safe. I am going to use a lock statement to lock the resource to make the call to the delegate thread safe.

Use lock to synchronize

You can use lock to lock the delegate before calling the methods on the delegates. The OnMyEvent method will look like:

protected virtual void OnMyEvent()
{
    EventHandler eventHandler = null;
    lock(this)
    //lock the current instance so that other threads cannot change del.
    {
            eventHandler = _myEvent;
    }
    if(eventHandler != null)
    {
       foreach(EventHandler handler in eventHandler.GetInvocationList())
       {
            try
            {
                    handler(this, new EventArgs());
            }
            catch(Exception e)
            {
                Console.WriteLine("Error in the handler {0}: {1}", 
                handler.Method.Name, e.Message);
            }
       }
    }
}

You can also use various other techniques for thread synchronization which includes volatile, the ReadWriterLock class, etc. Please read WeakEvents.aspx for a detailed discussion.

Other related articles

For an in-depth discussion and information about delegates and events, please refer to the following article: event_fundamentals.aspx.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here