C# – Events

Events

Two major roles attached when using delegates are:

  1. broadcaster – contains delegate field and decides when to broadcast by invoking the delegate.
  2. subscriber – method target recipients, when to commerce and halt listening by calling += and -= on the broadcaster’s delegate.

An event is a construct which exposes just the subset of delegate features required for the broadcaster/subscriber model. The main purpose is to prevent subscribers from interfering with one another.

The easiest way to declare an event is to put the event keyword in front of a delegate member.Code within the containing type has full access and can treat the event as a delegate. Code outside of the containing type can only perform += and -= operations on the event.

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
		{
			// Exit if nothing has changed
			if (price == value) return;			
			decimal oldPrice = price;
			price = value;
			// If invocation list not empty,
			if (PriceChanged != null)
			    // fire event.			
				PriceChanged (oldPrice, price);	
		}
	}
}

static void Main()
{
	var stock = new Stock ("MSFT");
	stock.PriceChanged += ReportPriceChange;
	stock.Price = 123;
	stock.Price = 456;
}

static void ReportPriceChange (decimal oldPrice, decimal newPrice)
{
	("Price changed from " + oldPrice + " to " + newPrice).Dump();
}

Standard Event Pattern

.Net framework defines the standard pattern for writing events for consistency across Framework and user code. The core of the standard event pattern is System.EventArgs

System.EventArgs

  • Base class for conveying information for an event.
  • Exposes data as properties or as read-only fields.
  • The name of the custom event data class should end with EventArgs.
  • To pass an object that does not contain any data use the Empty field.

Stock Example:
It uses EventArgs to convey old and new prices when PricedChanged event is fired.

Rules for delegate when you have EventArgs as subclass

  • It must have a void return type.
  • It must accept two arguments: type of object, EventArgs subclass.
  • Its name must end with EventHandler.

The Framework defines a generic delegate called System.EventHandler<> that meets the above rules.

public delegate void EventHandler<TEventArgs>
 (object source, TEventArgs e) where TEventArgs : EventArgs;
public class PriceChangedEventArgs : EventArgs
{
	public readonly decimal LastPrice;
	public readonly decimal 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;}
	
        //1. define event of the chosen delegate type.
	public event EventHandler<PriceChangedEventArgs> PriceChanged;

	//2. write a protected virtual method that fires the event.
        //rules - name must match the name of the event with prefixed On word
        //and accept a single EventArgs argument.
	protected virtual void OnPriceChanged (PriceChangedEventArgs e)
	{
		PriceChanged?.Invoke (this, e);
	}
	
	public decimal Price
	{
		get { return price; }
		set
		{
			if (price == value) return;
			decimal oldPrice = price;
			price = value;
			OnPriceChanged (new PriceChangedEventArgs (oldPrice, price));
		}
	}
}

static void Main()
{
	Stock stock = new Stock ("THPW");
	stock.Price = 27.10M;
	// Register with the PriceChanged event
	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% stock price increase!");
}

The predefined nongeneric EventHandler delegate can be used when an event doesn’t carry extra information

public class Stock
{
	string symbol;
	decimal price;
	
	public Stock (string symbol) { this.symbol = symbol; }
	
	public event EventHandler PriceChanged;
	
	protected virtual void OnPriceChanged (EventArgs e)
	{
		PriceChanged?.Invoke (this, e);
	}
	
	public decimal Price
	{
		get { return price; }
		set
		{
			if (price == value) return;
			price = value;
			OnPriceChanged (EventArgs.Empty);
		}
	}
}

static void Main()
{
	Stock stock = new Stock ("THPW");
	stock.Price = 27.10M;
	// Register with the PriceChanged event
	stock.PriceChanged += stock_PriceChanged;
	stock.Price = 31.59M;
}

static void stock_PriceChanged (object sender, EventArgs e)
{
	Console.WriteLine ("New price = " + ((Stock) sender).Price);
}

Event Accessors

Are implementations of its += and -= functions, default accessors are implemented implicitly and you can define event accessors explicitly also.

public class Stock
{
	string symbol;
	decimal price;
	
	public Stock (string symbol) { this.symbol = symbol; }
	
	private EventHandler _priceChanged;         // Declare a private delegate

	public event EventHandler PriceChanged
	{
		add    { _priceChanged += value; }		// Explicit accessor
		remove { _priceChanged -= value; }		// Explicit accessor
	}
	
	protected virtual void OnPriceChanged (EventArgs e)
	{
		_priceChanged?.Invoke (this, e);
	}
	
	public decimal Price
	{
		get { return price; }
		set
		{
			if (price == value) return;
			price = value;
			OnPriceChanged (EventArgs.Empty);
		}
	}
}

static void Main()
{
	Stock stock = new Stock ("THPW");
	stock.Price = 27.10M;
	// Register with the PriceChanged event
	stock.PriceChanged += stock_PriceChanged;
	stock.Price = 31.59M;
}

static void stock_PriceChanged (object sender, EventArgs e)
{
	Console.WriteLine ("New price = " + ((Stock) sender).Price);
}

Event Modifiers

Like methods, events can be virtual, overridden, abstract or sealed. It can also be static.

public class Foo
{
  public static event EventHandler<EventArgs> StaticEvent;
  public virtual event EventHandler<EventArgs> VirtualEvent;
}