Delegates and Events in C#

In this second part of a ten-part series that focuses intensely on C#, we will wrap up our discussion of delegates, then move on to one of the most important subjects in the language: events. This article is excerpted from chapter four of C# 3.0 in a Nutshell, Third Edition, A Desktop Quick Reference, written by Joseph Albahari and Ben Albahari (O'Reilly; ISBN: 0596527578). Copyright © 2007 O'Reilly Media, Inc. All rights reserved. Used with permission from the publisher. Available from booksellers or direct from O'Reilly Media.

Contributed by
Rating: 5 stars5 stars5 stars5 stars5 stars / 5
September 25, 2008
Rate this Article:
MEH MEH++


SEARCH ASP FREE
TOOLS YOU CAN USE

advertisement

Delegate Compatibility

Type compatibility

Delegate types are all incompatible with each other, even if their signatures are the same:

  delegate void D1();
  delegate void D2();
  ...

  D1 d1 = Method1;
  D2 d2 = d1;                       // compile-time error

Delegate instances are considered equal if they have the same method targets:

  delegate void D();
  ...

  D d1 = Method1;
  D d2 = Method1;
  Console.WriteLine (d1 == d2);    // true

Parameter compatibility

When you call a method, you can supply arguments that have more specific types than the parameters of that method. This is ordinary polymorphic behavior. For exactly the same reason, a delegate can have more specific parameter types than its method target. This is called contravariance.

Consider the following example:

  delegate void SpecificDelegate (SpecificClass s);

  class SpecificClass {}

  class Test
  {
    static void Main()
    {
      SpecificDelegate specificDelegate = GeneralHandler;
      specificDelegate (new SpecificClass());
    }

    static void GeneralHandler(object o)
    {
      Console.WriteLine(o.GetType()); // SpecificClass
    }
  }

A delegate merely calls a method on someone else’s behalf. In this case, theSpecificDelegateis invoked with an argument of typeSpecificClass. When the argument is then relayed to the target method, the argument gets implicitly upcast to anobject.

The standard event pattern is designed to help you leverage contravariance through its use of the commonEventArgsbase class. For example, you can have a single method invoked by two different delegates, one passing aMouseEventArgsand the other passing aKeyEventArgs.

Return type compatibility

If you call a method, you may get back a type that is more specific than what you asked for. This is ordinary polymorphic behavior. For exactly the same reason, the return type of a delegate can be less specific than the return type of its target method. This is called covariance. Consider the following example:

  delegate Asset DebtCollector();

  class Asset {}

  class House : Asset {}

  class Test
  {
    static void Main()
    
{
      DebtCollector d = new DebtCollector (GetHomeSweetHome);
      
Asset a = d();
     
Console.WriteLine(a.GetType()); // House
   
}
    
static House GetHomeSweetHome() {return new House(); }
  }

A delegate merely calls a method on someone else’s behalf. In this case, theDebtCollectorexpects to get back anAsset—but anyAssetwill do. Delegate return types are said to be covariant.

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. Aneventis a wrapper for a delegate 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.

To declare an event member, you put theevent keyword in front of a delegate member. For instance:

  public class Broadcaster
  {
    public event ProgressReporter Progress;
  }

Code within theBroadcastertype has full access toProgressand can treat it as a delegate. Code outside ofBroadcastercan only perform+=and-=operations onProgress.

Consider the following example. TheStockclass invokes itsPriceChangedevent every time thePriceof theStock 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 PriceChanged PriceChanged;

    public decimal Price
    {
      get { return price; }
     
set
      {
       
if (price == value) return;       // exit if nothing has changed
       
if (PriceChanged != null)         // if invocation list not empty
        PriceChanged (price, value); // fire event
        price = value;
      }
    }
  }

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

  1. Replace other subscribers by reassigningPriceChanged(instead of using the+=operator).
  2. Clear all subscribers (by settingPriceChangedtonull).
  3. Broadcast to other subscribers by invoking the delegate.

Standard Event Pattern

The .NET Framework defines a standard pattern for writing events. Its purpose is to provide consistency across both Framework and user code. At the core of the standard event pattern is System.EventArgs: a predefined Framework class with no members (other than the static Empty property).EventArgsis a base class for conveying information for an event. In ourStockexample, we would subclassEventArgsto convey the old and new prices when aPriceChangedevent is fired:

  public class PriceChangedEventArgs : System.EventArgs
 
{
   
public readonly decimal LastPrice;
   
public readonly decimal NewPrice;

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

For reusability, theEventArgssubclass is named according to the information it contains (rather than the event for which it will be used). It typically exposes data as properties or as read-only fields.

With anEventArgssubclass in place, the next step is to choose or define a delegate for the event. There are three rules:

  • It must have avoidreturn type.
  • It must accept two arguments: the first of typeobject, and the second a subclass ofEventArgs. The first argument indicates the event broadcaster, and the second argument contains the extra information to convey.
  • Its name must end in “EventHandler”.

The Framework defines a generic delegate calledSystem.EventHandler<>that satisfies these rules:

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

Before generics existed in the language (prior to C# 2.0), we would have had to instead write a custom delegate as follows:

  public delegate void PriceChangedHandler (object sender,
    PriceChangedEventArgs e);

For historical reasons, most events within the Framework use delegates defined in this way.

The next step is to define an event of the chosen delegate type. Here, we use the genericEventHandler delegate:

  public class Stock
  {
   
...

    public event EventHandler<PriceChangedEventArgs>PriceChanged;
 
} 

Finally, the pattern requires that you write a protected virtual method that fires the event. The name must match the name of the event, prefixed with the word “On”, and then accept a singleEventArgsargument:

  public class Stock
  {
    ...

    public event EventHandler<PriceChangedEventArgs> PriceChanged;

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

This provides a central point from which subclasses can invoke or override the event.

Here’s the complete example:

  using System;

  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;}

    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;
     
}
    }
  }

  class Test
  {
    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 nongenericEventHandler delegate can be used when an event doesn’t carry extra information. In this example, we rewriteStocksuch that thePriceChangedevent is fired after the price changes, and no information about the event is necessary, other than it happened. We also make use of theEventArgs.Emptyproperty, in order to avoid unnecessarily instantiating an instance ofEventArgs.

  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);
      }
    }
  }

Event Accessors

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 thePriceChangedevent from our previous example:

  private EventHandler _PriceChanged;     // declare a private delegate

  public event EventHandler PriceChanged
  {
    add
    {
     
_PriceChanged += value;
    }
    remove
    {
      _PriceChanged -= value;
    }
 
}

This example is functionally identical to C#'s default accessor implementation. The add and remove keywords after the event declaration instruct C# not to generate a default field and accessor logic.

With explicit event accessors, you can apply more complex strategies to the storage and access of the underlying delegate. There are three scenarios where this is useful:

  1. When the event accessors are merely relays for another class that is broadcasting the event.
  2. When the class exposes a large number of events, where most of the time very few subscribers exist, such as a Windows control. In such cases, it is better to store the subscriber’s delegate instances in a dictionary, since a dictionary will contain less storage overhead than dozens of null delegate field references.
  3. When explicitly implementing an interface that declares an event.

Here is an example that illustrates the last point:

  public interface IFoo
  {
   
event EventHandler Ev;
  }

  class Foo : IFoo
  {
   
private EventHandler ev;

    event EventHandler IFoo.Ev
   
{
     
add    { ev += value; }
     
remove { ev -= value; }
   
}
  }

Theaddandremove parts of an event are compiled toadd_XXX  andremove_XXX methods.

The+=and-=operations on an event are compiled to calls to theadd_XXX andremove_XXX methods.

Event Modifiers

Like methods, events can be virtual, overridden, abstract, and sealed. Events can also be static:

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

Please check back next week for the continuation of this article.

blog comments powered by Disqus
C# ARTICLES

- Beginning C#
- ASP.NET RedirectPermanent Method using C# an...
- C Programming Language and UNIX Pioneer Pass...
- Using Facebook JavaScript SDK in ASP.NET wit...
- ASP.NET Export to Excel and Word using VB.NE...
- WAV and MP3 Streaming with ASP.Net and C#
- Game Programming using SDL: the File I/O API
- C# and Java Developer Jobs on the Rise
- The Future Evolution of C# and VB.NET
- C# If and Else-if Statements
- How To Use the C# String Replace Method
- 5 Ways to Parse XML in C#
- C# Meets Design Patterns
- Coding a CRC-Generating Algorithm in C
- Cyclic Redundancy Check

ASP Web Hosting ASP.Net Web Hosting Windows Web Hosting
ASP Free Forums 
 RSS  Tutorials RSS
 RSS  Forums RSS
 RSS  All Feeds
Site Map 
Request Media Kit
Write For Us Get Paid 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Privacy Policy 
Support 


© 2003-2012 by Developer Shed. All rights reserved. DS Cluster 7 - Follow our Sitemap
Most Popular Topics
All ASP.Net Tutorials