Export (0) Print
Expand All

Events Tutorial

Visual Studio .NET 2003

This tutorial shows how to declare, invoke, and hook up to events in C#.

Sample Files

See Events Sample to download and build the sample files discussed in this tutorial.

Further Reading

Tutorial

An event in C# is a way for a class to provide notifications to clients of that class when some interesting thing happens to an object. The most familiar use for events is in graphical user interfaces; typically, the classes that represent controls in the interface have events that are notified when the user does something to the control (for example, click a button).

Events, however, need not be used only for graphical interfaces. Events provide a generally useful way for objects to signal state changes that may be useful to clients of that object. Events are an important building block for creating classes that can be reused in a large number of different programs.

Events are declared using delegates. If you have not yet studied the Delegates Tutorial, you should do so before continuing. Recall that a delegate object encapsulates a method so that it can be called anonymously. An event is a way for a class to allow clients to give it delegates to methods that should be called when the event occurs. When the event occurs, the delegate(s) given to it by its clients are invoked.

In addition to the examples on declaring, invoking, and hooking up to events, this tutorial also introduces the following topics:

  • Events and Inheritance
  • Events in Interfaces
  • .NET Framework Guidelines

Example 1

The following simple example shows a class, ListWithChangedEvent, which is similar to the standard ArrayList class, but also invokes a Changed event whenever the contents of the list change. Such a general-purpose class could be used in numerous ways in a large program.

For example, a word processor might maintain a list of the open documents. Whenever this list changes, many different objects in the word processor might need to be notified so that the user interface could be updated. By using events, the code that maintains the list of documents doesn't need to know who needs to be notified — once the list of documents is changed, the event is automatically invoked and every object that needs to be notified is correctly notified. By using events, the modularity of the program is increased.

// events1.cs
using System;
namespace MyCollections 
{
   using System.Collections;

   // A delegate type for hooking up change notifications.
   public delegate void ChangedEventHandler(object sender, EventArgs e);

   // A class that works just like ArrayList, but sends event
   // notifications whenever the list changes.
   public class ListWithChangedEvent: ArrayList 
   {
      // An event that clients can use to be notified whenever the
      // elements of the list change.
      public event ChangedEventHandler Changed;

      // Invoke the Changed event; called whenever list changes
      protected virtual void OnChanged(EventArgs e) 
      {
         if (Changed != null)
            Changed(this, e);
      }

      // Override some of the methods that can change the list;
      // invoke event after each
      public override int Add(object value) 
      {
         int i = base.Add(value);
         OnChanged(EventArgs.Empty);
         return i;
      }

      public override void Clear() 
      {
         base.Clear();
         OnChanged(EventArgs.Empty);
      }

      public override object this[int index] 
      {
         set 
         {
            base[index] = value;
            OnChanged(EventArgs.Empty);
         }
      }
   }
}

namespace TestEvents 
{
   using MyCollections;

   class EventListener 
   {
      private ListWithChangedEvent List;

      public EventListener(ListWithChangedEvent list) 
      {
         List = list;
         // Add "ListChanged" to the Changed event on "List".
         List.Changed += new ChangedEventHandler(ListChanged);
      }

      // This will be called whenever the list changes.
      private void ListChanged(object sender, EventArgs e) 
      {
         Console.WriteLine("This is called when the event fires.");
      }

      public void Detach() 
      {
         // Detach the event and delete the list
         List.Changed -= new ChangedEventHandler(ListChanged);
         List = null;
      }
   }

   class Test 
   {
      // Test the ListWithChangedEvent class.
      public static void Main() 
      {
      // Create a new list.
      ListWithChangedEvent list = new ListWithChangedEvent();

      // Create a class that listens to the list's change event.
      EventListener listener = new EventListener(list);

      // Add and remove items from the list.
      list.Add("item 1");
      list.Clear();
      listener.Detach();
      }
   }
}

Output

This is called when the event fires.
This is called when the event fires.

Code Discussion

  • Declaring an event   To declare an event inside a class, first a delegate type for the event must be declared, if none is already declared.
    public delegate void ChangedEventHandler(object sender, EventArgs e);
    

    The delegate type defines the set of arguments that are passed to the method that handles the event. Multiple events can share the same delegate type, so this step is only necessary if no suitable delegate type has already been declared.

    Next, the event itself is declared.

    public event ChangedEventHandler Changed;
    

    An event is declared like a field of delegate type, except that the keyword event precedes the event declaration, following the modifiers. Events usually are declared public, but any accessibility modifier is allowed.

  • Invoking an event   Once a class has declared an event, it can treat that event just like a field of the indicated delegate type. The field will either be null, if no client has hooked up a delegate to the event, or else it refers to a delegate that should be called when the event is invoked. Thus, invoking an event is generally done by first checking for null and then calling the event.
    if (Changed != null)
    Changed(this, e);
    

    Invoking an event can only be done from within the class that declared the event.

  • Hooking up to an event   From outside the class that declared it, an event looks like a field, but access to that field is very restricted. The only things that can be done are:
    • Compose a new delegate onto that field.
    • Remove a delegate from a (possibly composite) field.

    This is done with the += and -= operators. To begin receiving event invocations, client code first creates a delegate of the event type that refers to the method that should be invoked from the event. Then it composes that delegate onto any other delegates that the event might be connected to using +=.

    // Add "ListChanged" to the Changed event on "List":
    List.Changed += new ChangedEventHandler(ListChanged);
    

    When the client code is done receiving event invocations, it removes its delegate from the event by using operator -=.

    // Detach the event and delete the list:
    List.Changed -= new ChangedEventHandler(ListChanged);
    

Events and Inheritance

When creating a general component that can be derived from, what seems to be a problem sometimes arises with events. Since events can only be invoked from within the class that declared them, derived classes cannot directly invoke events declared within the base class. Although this is sometimes what is desired, often it is appropriate to give the derived class the freedom to invoke the event. This is typically done by creating a protected invoking method for the event. By calling this invoking method, derived classes can invoke the event. For even more flexibility, the invoking method is often declared as virtual, which allows the derived class to override it. This allows the derived class to intercept the events that the base class is invoking, possibly doing its own processing of them.

In the preceding example, this has been done with the OnChanged method. A derived class could call or override this method if it needed to.

Events in Interfaces

One other difference between events and fields is that an event can be placed in an interface while a field cannot. When implementing the interface, the implementing class must supply a corresponding event in the class that implements the interface.

.NET Framework Guidelines

Although the C# language allows events to use any delegate type, the .NET Framework has some stricter guidelines on the delegate types that should be used for events. If you intend for your component to be used with the .NET Framework, you probably will want to follow these guidelines.

The .NET Framework guidelines indicate that the delegate type used for an event should take two parameters, an "object source" parameter indicating the source of the event, and an "e" parameter that encapsulates any additional information about the event. The type of the "e" parameter should derive from the EventArgs class. For events that do not use any additional information, the .NET Framework has already defined an appropriate delegate type: EventHandler.

Example 2

The following example is a modified version of Example 1 that follows the .NET Framework guidelines. The example uses the EventHandler delegate type.

// events2.cs
using System;
namespace MyCollections 
{
   using System.Collections;

   // A class that works just like ArrayList, but sends event
   // notifications whenever the list changes:
   public class ListWithChangedEvent: ArrayList 
   {
      // An event that clients can use to be notified whenever the
      // elements of the list change:
      public event EventHandler Changed;

      // Invoke the Changed event; called whenever list changes:
      protected virtual void OnChanged(EventArgs e) 
      {
         if (Changed != null)
            Changed(this,e);
      }

      // Override some of the methods that can change the list;
      // invoke event after each:
      public override int Add(object value) 
      {
         int i = base.Add(value);
         OnChanged(EventArgs.Empty);
         return i;
      }

      public override void Clear() 
      {
         base.Clear();
         OnChanged(EventArgs.Empty);
      }

      public override object this[int index] 
      {
         set 
         {
            base[index] = value;
            OnChanged(EventArgs.Empty);
         }
      }
   }
}

namespace TestEvents 
{
   using MyCollections;

   class EventListener 
   {
      private ListWithChangedEvent List;

      public EventListener(ListWithChangedEvent list) 
      {
         List = list;
         // Add "ListChanged" to the Changed event on "List":
         List.Changed += new EventHandler(ListChanged);
      }

      // This will be called whenever the list changes:
      private void ListChanged(object sender, EventArgs e) 
      {
         Console.WriteLine("This is called when the event fires.");
      }

      public void Detach() 
      {
         // Detach the event and delete the list:
         List.Changed -= new EventHandler(ListChanged);
         List = null;
      }
   }

   class Test 
   {
      // Test the ListWithChangedEvent class:
      public static void Main() 
      {
      // Create a new list:
      ListWithChangedEvent list = new ListWithChangedEvent();

      // Create a class that listens to the list's change event:
      EventListener listener = new EventListener(list);

      // Add and remove items from the list:
      list.Add("item 1");
      list.Clear();
      listener.Detach();
      }
   }
}

Output

This is called when the event fires.
This is called when the event fires.

See Also

C# Tutorials

Show:
© 2014 Microsoft