Events
Two major roles attached when using delegates are:
- broadcaster – contains delegate field and decides when to broadcast by invoking the delegate.
- 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; }