Instruqt: Events

Nuts and Bolts

Events are the formalisation of the subscriber / publisher pattern we see when multicasting delegates. We have the invoker of the delegate, the “publisher”, and all the target methods which are executed, the “subscribers”.

We could stick with delegates to achieve the desired results, but the fact that subscribers can interfere with one another prevents this from being a great solution. Subscribers could reset the delegate by setting it to null, thereby removing all subscribers, and they could remove specific subscribers by using the -= operator.

Events exist to prevent subscribers from interfering with one another. This is achieved by wrapping delegate functionality to make available only the subset of functionality required to support the publisher / subscriber model.

Events are declared as follows:

public class Broadcaster {
    public event NotifyThingy Notifyer; 
}

In the above declaration, NotifyThingy would be a delegate, declared within scope as follows:

public delegate void NotifyThingy(string message);

So it’s clear from the example above that delegates are driving the event implementation, which makes sense as events are just a wrapper as mentioned before.

The Standard Event Pattern

A standard pattern exists for event implementations which completes the formalisation of the pub/sub model. The pattern exists of three parts:

  1. The event declaration
  2. An EventArgs implementation for conveying information within the event
  3. A function to wrap event invocation by the publisher

Here is an example:

public class Stock {
    public EventHandler<PriceChangedEventArgs> PriceChanged;

    protected virtual void OnPriceChanged(int oldPrice, int newPrice) {
      if (PriceChanged != null)
        PriceChanged(this, new PriceChangedEventArgs(oldPrice, newPrice, Symbol));
    }

    public readonly string Symbol;

    public Stock(string symbol) {
      Symbol = symbol;
    }

    public int Price {
      get { return _price; }
      set {
        if (_price != value)
          OnPriceChanged(_price, value);

        _price = value;
      }
    }
}

public class PriceChangedEventArgs : EventArgs {
  public readonly int OldPrice;
  public readonly int NewPrice;
  public readonly string Symbol;

  public PriceChangedEventArgs(int oldPrice, int newPrice, string symbol) {
    OldPrice = oldPrice;
    NewPrice = newPrice;
    Symbol = symbol;
  }
}

A few things to take notice of:

  1. The generic EventHandler<T> delegate
  2. The fact that the invocation wrapper function is protected virtual, meaning subclasses can fire the event, and execute code before and after the event.
  3. The null check in the event invocation wrapper function

The EventHandler<T> delegate means we can forgo the need to declare an underlying delegate explicitly for our events. A non-generic EventHander delegate also exists for when we don’t need to pass anything in the event.