Events

When using delegates, two emergent roles commonly appear: broadcaster and subscriber. The broadcaster is a type that contains a delegate field. The broadcaster decides when to broadcast, by invoking the delegate. The subscribers are the method target recipients. A subscriber decides when to start and stop listening, by calling += and −= on the broadcaster’s delegate. A subscriber does not know about, or interfere with, other subscribers.

Events are a language feature that formalizes this pattern. An event is a construct that exposes just the subset of delegate features required for the broadcaster/subscriber model. The main purpose of events is to prevent subscribers from interfering with each other.

The easiest way to declare an event is to put the event keyword in front of a delegate member:

public class Broadcaster
{
  public event ProgressReporter Progress;
}

Code within the Broadcaster type has full access to Progress and can treat it as a delegate. Code outside of Broadcaster can only perform += and −= operations on the Progress event.

In the following example, the Stock class fires its PriceChanged event every time the Price of the Stock changes:

public delegate void PriceChangedHandler
 (decimal oldPrice, decimal newPrice);
public class Stock
{
  string symbol; decimal price;

  public Stock (string symbol) { this.symbol = symbol; }

  public event PriceChangedHandler PriceChanged;

  public decimal Price
  {
    get { return price; }
    set
    {
      if (price == value) return;
      // Fire event if invocation list isn't empty:
      if (PriceChanged != null)
        PriceChanged (price, value);
      price = value;
    }
  }
}

If we remove the event keyword from our example so that PriceChanged becomes an ordinary delegate field, our example would give the same results. However, Stock would be less robust, in that subscribers could do the following things to interfere with each other:

  • Replace other subscribers by reassigning PriceChanged (instead of using the += operator).

  • Clear all subscribers (by setting PriceChanged to null).

  • Broadcast to other subscribers by invoking the delegate.

Events can be virtual, overridden, abstract, or sealed. They can also be static.

The .NET Framework defines a standard pattern for writing events. Its purpose is to provide consistency across both Framework and user code. Here is the preceding example refactored with this pattern:

public class PriceChangedEventArgs : EventArgs
{
  public readonly decimal LastPrice, NewPrice;

  public PriceChangedEventArgs (decimal lastPrice,
                                decimal newPrice)
  {
    LastPrice = lastPrice; NewPrice = newPrice;
  }
}

public class Stock
{
  string symbol; decimal price;

  public Stock (string symbol) { this.symbol = symbol; }

  public event EventHandler<PriceChangedEventArgs>
               PriceChanged;

  protected virtual void OnPriceChanged
                        (PriceChangedEventArgs e)
  {
    if (PriceChanged != null) PriceChanged (this, e);
  }

  public decimal Price
  {
    get { return price; }
    set
    {
      if (price == value) return;
      OnPriceChanged (new PriceChangedEventArgs (price,
                                                 value));
      price = value;
    }
  }
}

At the core of the standard event pattern is System.EventArgs: a predefined Framework class with no members (other than the static Empty property). EventArgs is a base class for conveying information for an event. In this example, we subclass EventArgs to convey the old and new prices when a PriceChanged event is fired.

The generic System.EventHandler delegate is also part of the .NET Framework and is defined as follows:

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

A protected virtual method, named On-event-name, centralizes firing of the event. This allows subclasses to fire the event (which is usually desirable) and also allows subclasses to insert code before and after the event is fired.

Here’s how we could use our Stock class:

static void Main()
{
  Stock stock = new Stock ("THPW");
  stock.Price = 27.10M;

  stock.PriceChanged += stock_PriceChanged;
  stock.Price = 31.59M;
}

static void stock_PriceChanged
  (object sender, PriceChangedEventArgs e)
{
  if ((e.NewPrice - e.LastPrice) / e.LastPrice > 0.1M)
    Console.WriteLine ("Alert, 10% price increase!");
}

For events that don’t carry additional information, the Framework also provides a nongeneric EventHandler delegate. We can demonstrate this by rewriting our Stock class such that the PriceChanged event fires after the price changes. This means that no additional information need be transmitted with the event:

public class Stock
{
  string symbol; decimal price;

  public Stock (string symbol) {this.symbol = symbol;}

  public event EventHandler PriceChanged;

  protected virtual void OnPriceChanged (EventArgs e)
  {
    if (PriceChanged != null) PriceChanged (this, e);
  }

  public decimal Price
  {
    get { return price; }
    set
    {
      if (price == value) return;
      price = value;
      OnPriceChanged (EventArgs.Empty);
    }
  }
}

Note that we also used the EventArgs.Empty property—this saves instantiating an instance of EventArgs.

An event’s accessors are the implementations of its += and −= functions. By default, accessors are implemented implicitly by the compiler. Consider this event declaration:

public event EventHandler PriceChanged;

The compiler converts this to the following:

  • A private delegate field.

  • A public pair of event accessor functions, whose implementations forward the += and −= operations to the private delegate field.

You can take over this process by defining explicit event accessors. Here’s a manual implementation of the PriceChanged event from our previous example:

EventHandler _priceChanged;   // Private delegate

public event EventHandler PriceChanged
{
  add    { _priceChanged += value; }
  remove { _priceChanged −= value; }
}

This example is functionally identical to C#’s default accessor implementation (except that C# also ensures thread safety around updating the delegate). By defining event accessors ourselves, we instruct C# not to generate default field and accessor logic.

With explicit event accessors, you can apply more complex strategies to the storage and access of the underlying delegate. This is useful when the event accessors are merely relays for another class that is broadcasting the event, or when explicitly implementing an interface that declares an event:

public interface IFoo { event EventHandler Ev; }
class Foo : IFoo
{
  EventHandler ev;
  event EventHandler IFoo.Ev
  {
    add { ev += value; } remove { ev −= value; }
  }
}