Events

.Events
.NET Events | Publisher/Subscribers Code | Event Following .NET Standards

"The main purpose for events is to prevent subscribers from interfering with each other."

Events enable a object to notify other objects when something of interest occurs. The object that sends (or raises) the event is called the publisher. The objects which receive (or handle) the event are called subscribers. Events are used extensively in graphical user interfaces to indicate when controls, such as buttons and list boxes, have been activated. Visual studio can automatically generate empty event handler methods and the code to subscribe to events. However, events can also be used in non-graphical applications as well.

Events in the .NET Framework are based on the delegate model. The delegate model follows the publisher/subscribers design pattern, which allows subscribers to register with, and receive notifications from, a publisher. An event is a construct that exposes a subset of delegate features which provide protection for the subscribers. The publisher/subscribers model will work with a delegate instead of an event, but that would allow the subscribers the ability to interfere with each other. The main purpose for events is to prevent subscribers from interfering with each other. If a delegate were used instead of an event, any subscriber would be able to unsubscribe all the subscribers from the event. A subscriber would also be able to broadcast to other subscribers by using the delegate. These action are prevented by using an event instead of a delegate.

Events have the following properties and behaviors:

  • The publisher determines when an event is raised; the subscribers determine what action is taken in response to the event.

  • An event can have multiple subscribers. A subscriber can handle multiple events from multiple publishers.

  • Events that have no subscribers are never raised.

  • When an event has multiple subscribers, the event handlers are invoked synchronously when an event is raised. To invoke events asynchronously, see Calling Synchronous Methods Asynchronously.

  • In the .NET Framework class library, events are based on the EventHandler delegate and the EventArgs base class.




.NET Events

.Net uses the EventArgs as the base class for conveying information for an event. For example, the System.AssemblyLoadEventArgs class derives from EventArgs and is used to hold the data for assembly load events. To create a custom event data class, create a class that derives from the EventArgs class and provide the properties to store the necessary data. The name of your custom event data class should end with EventArgs. To pass an object that does not contain any data, use the Empty field.

Generic Delegate for Event Handler

A delegate must be chosen for the event which has a standard signature. That is, the return type is void, the first parameter is of type object, and the second parameter is a subclass of the EventArgs class. .Net provides a generic delegate which can be used for the event handler:

public delegate void EventHandler<TEventArgs>
(object source, TEventArgs e) where TEventArgs : EventArgs;


Historical Method of Coding Delegate for Event Handler

However many of the event handlers are coded in the pattern used before generics was introduced in the .NET framework. That pattern is:

public delegate void ChangeHandler (object sender, ChangeEventArgs e);


Defining the Event

An event must be defined in the Publisher class that matches the delegate type. Events can only be invoked from within the class or struct where they are declared (the publisher class). Events can be marked as: public, private, protected, internal, or protected internal. These access modifiers define how users of the class can access the event. The following keywords can also be applied to events: static, virtual, sealed, or abstract.

public event EventHandler Change;


Method To Fire Event

A protected virtual method needs to be coded in the Publisher class to fire the event. The name of the method must match the name of the event, prefixed with the word On. The method must accept a single EventArgs argument.

protected virtual void onChange (ChangeEventArgs e)
{
   if (Change ! = null)
     Change (this, e);
}

Note: If using more than one thread, the delegate needs to be assigned to a temporary variable before testing and invoking in order to be thread-safe.


Subscribing to the Event

A subscriber decides when to start or stop listening for the event by calling the += and -= operators on the event delegate. When using an Event (instead of just a delegate) the subscribers in unaware of other subscribers, and unable to interfere with other subscribers.

 m.MyEvent += new Publisher.MyEventHandler(Sub1Action);
Top




Publisher/Subscribers Code

.C# Example Publisher/Subscribers Code
C# Event Example Code

The following program uses events to implement the Publisher/Subscribers pattern. One publisher object which will raise an event every second. Then two subscribers who are listening for the event will perform an action once the event occurs. (i.e. they will "handle the event"). The second subscriber is coded to halt the program after it handles 4 events.

using System;
namespace csharpEvent
{
  /*******************************
   *      Publisher Class        *
   *******************************/
   public class Publisher
   {
      // 1. Create event arguments class
      public EventArgs e = null;

      // 2. Create a delegate for event handler
      public delegate void MyEventHandler(Publisher m, EventArgs e);

      // 3. Create the event
      public event MyEventHandler MyEvent;

      // 4. Create method to start the publisher
      public void Start()
      {
          while (true)
          {
              System.Threading.Thread.Sleep(1000);
              if (MyEvent != null) MyEvent(this, e);
          }
      }
   }

  /*******************************
   *   First Subscriber Class    *
   *******************************/
   public class Subscriber1
   {
       public void Subscribe(Publisher m)
       {
           // 1. Subscribe to the event, define action to take
           m.MyEvent += new Publisher.MyEventHandler(Sub1Action);
       }

       // 2. Define the action to take (event handler)
       private void Sub1Action(Publisher m, EventArgs e)
       {
          System.Console.WriteLine("Subscriber 1: Do Something");
       }
   }

  /*******************************
   *   Second Subscriber Class   *
   *******************************/
   public class Subscriber2
   {
       private long eventCount = 1;

       public void Subscribe(Publisher m)
       {
           // Subscribe to the event, define action to take
           m.MyEvent += new Publisher.MyEventHandler(Sub2Action);
       }

       // Define the action to take (event handler)
       private void Sub2Action(Publisher m, EventArgs e)
       {
           System.Console.WriteLine("Subscriber 2: Do Something Else\n");

           switch (eventCount % 2)
           {
              case 0 :
                 System.Console.WriteLine("*** Event Count is: {0}\n", eventCount);
                 break;
           }
           if (eventCount > 3)
           {
               Console.WriteLine("\nSubscriber 2 is halting the program after {0} events.\n", eventCount);
               Environment.Exit(0);
           }
           eventCount++;
       }
   }


   class Program
   {
      static void Header()
      {
         System.Console.WriteLine("************************************************************");
         System.Console.WriteLine("***  Event: One Publisher, Two Subscribers, No Arguments ***");
         System.Console.WriteLine("************************************************************\n");
      }

      static void Main()
      {
         Header();
         Publisher publisher = new Publisher();        // Create Publisher Object
         Subscriber1 subscriberl = new Subscriber1();  // Create First Subscriber Object
         Subscriber2 subscriber2 = new Subscriber2();  // Create Second Subscriber Object
         subscriberl.Subscribe(publisher);             // Sub1: Subscribe to the Publisher
         subscriber2.Subscribe(publisher);             // Sub2: Subscribe to the Publisher
         publisher.Start();                            // Start the publisher
      }
   }
}

Top



Event Following .NET Standards

.Event Following .NET Standards
Event Following .NET Standards

For compatibility with the .NET Framework, events should be published based on the EventHandler delegate. A popular, and recommended option, is to use the generic version called EventHandler. The advantage of using EventHandler is that you do not need to code your own custom delegate if your event generates event data. You simply provide the type of the event data object as the generic parameter.

using System;
using System.Collections.Generic;

namespace dotNetEvent
{
    // 1. Create class for event arguments at scope
    //    visible to both publisher and subscriber
   /*******************************
    *   EventArgs Derived Class   *
    *******************************/
    public class MyEventArgs : EventArgs
    {
        public MyEventArgs(string s)
        {
            arg1 = s;
        }
        private string arg1;

        public string Arg1
        {
            get { return arg1; }
            set { arg1 = value; }
        }
    }

   /*******************************
    *      Publisher Class        *
    *******************************/
    class Publisher
    {
        // 2. Create the event using EventHandler<T>,
        //    This allows you to skip declaring a delegate.
        public event EventHandler<MyEventArgs> RaiseMyEvent;

        // 3. Create method to do work and call will
        //    call the method that raises the event.
        public void PubProcessing()
        {
            // Note: Work can be performed prior to
            //       or after the event is raised.
            OnRaiseMyEvent(new MyEventArgs("Event arg1"));
        }

        // 4. Declare protected virtual method to allow derived
        // classes to override the event invocation behavior
        protected virtual void OnRaiseMyEvent(MyEventArgs e)
        {
            // Make a temporary copy of the event to avoid possibility of
            // a race condition if the last subscriber unsubscribes
            // immediately after the null check and before the event is raised.
            EventHandler<MyEventArgs> myHandler = RaiseMyEvent;

            // Event is null if there are no subscribers
            if (myHandler != null)
            {
                // Format the string to send inside the MyEventArgs parameter
                e.Arg1 += String.Format(" at {0}", DateTime.Now.ToString());

                // Raise the event.
                myHandler(this, e);
            }
        }
    }

   /*******************************
    *   Subscribers Class         *
    *******************************/
    class Subscriber
    {
        private string id;
        public Subscriber(string ID, Publisher pub)
        {
            id = ID;

            // 1. Subscribe to the event
            pub.RaiseMyEvent += HandleMyEvent;
        }

        // 2. Define Action to take (event handler).
        void HandleMyEvent(object sender, MyEventArgs e)
        {
            Console.WriteLine(id + " argument 1: {0}", e.Arg1);
        }
    }

    class Program
    {
        static void Header()
        {
            Console.WriteLine("************************************************************");
            Console.WriteLine("***  Event: One Publisher, Two Subscribers, One Argument ***");
            Console.WriteLine("***                                                      ***");
            Console.WriteLine("***      1. Conforms to .NET framework guidelines.       ***");
            Console.WriteLine("***      2. Uses MyEventArgs to pass arguments to event. ***");
            Console.WriteLine("***      3. Uses EventHandler<T>.                        ***");
            Console.WriteLine("************************************************************\n");
        }
       
        static void Main(string[] args)
        {
            Header();
            Publisher publisher = new Publisher(); // Create Publisher Object
            Subscriber subscriber1 = new Subscriber("-> subscriber1", publisher); // Create First Subscriber Object
            Subscriber subscriber2 = new Subscriber("-> subscriber2", publisher); // Create Second Subscriber Object
            publisher.PubProcessing();             // Call the method that raises the event.
            Console.WriteLine();
        }
    }
}

Top


Reference Articles

Top