Friday, December 08, 2006

Returning values from C# event handlers

C# events are typically used when a class instance that needs to notify other class instances that reference it that something has taken place.

In some cases, it is useful for an event handler to respond to the event by sending data back to the instance that fired the event. For example, in response to an event fired to give notification that a particular operation is starting, an event handler for the event could respond by returning a boolean value to indicate that the operation should either proceed normally or be aborted.

There are a couple of ways in which returning data in response to handling an event in this manner can be set up.

One is for the event handler method to set a parameter on one of the event method arguments. The System.ComponentModel.CancelEventArgs class is a good example of this. The CancelEventArgs class has a public Boolean property, Cancel, that the event handler method can set to True to indicate that the operation associated with the fired event should be aborted.

A second way for an event handler method to return a value back to the sender is for the delegate that the event is based on to include just that -- a return value. Instead of being declared to return void, the delegate on which the event is based can be defined to return a type such as bool or int.

What happens, though, if there is more than one handler for the event, both of which set the return value (either in a property of one of the event method arguments, or in the return value itself)?

The answer (at least in a single-threaded scenario) is “last-one-in-wins” -- that is, the last event handler method to run is the one whose return value will be received by the code that fired the event. (Event handlers run in the order in which they were registered. A good example of this which uses just plain delegates (not events) can be found in section 15.3 of the C# Language Specification.)

For example, consider this code, which wires up 3 separate event handlers to the event fired by the EventSource class, each of which set the value of the Data parameter of the event’s argument:

class Program
{
  public static void Main(string[] args)
  {
    EventSource eventSource = new EventSource(); 
    Handler handler1 = new Handler(1, eventSource);
    Handler handler2 = new Handler(2, eventSource);
    Handler handler3 = new Handler(3, eventSource);

    eventSource.FireEvent();
  }
}

/// 
/// The IntEventArgs class extends the basic EventArgs class 
/// to add a public parameter of type int named Data.
/// 
class IntEventArgs : EventArgs
{
  public int Data = -1;
}

/// 
/// The EventSource class fires an event that passes an 
/// IntEventArgs as a parameter.
/// 
class EventSource
{
  public delegate void ReturnIntHandler
    (object sender, IntEventArgs eventArgs);
  public event ReturnIntHandler ReturnIntEvent;

  public void FireEvent()
  {
    Console.Out.WriteLine("[EventSource] Firing event...");

    IntEventArgs eventArgs = new IntEventArgs();
    ReturnIntHandler e = this.ReturnIntEvent;
    if (e != null)
    {
      e(this, eventArgs);
    }

    Console.Out.WriteLine("[EventSource] Finished firing event. "
      + "eventArgs.Data: " + eventArgs.Data);
  }
}

/// 
/// The Handler class gets assigned an ID value when it is created 
/// (to distinguish it from other instances of the Handler class), 
/// and handles events of type ReturnIntEvent.
/// 
class Handler
{
  private int _id;

  public Handler(int id, EventSource eventSource)
  {
    this._id = id;
    eventSource.ReturnIntEvent +=
      new EventSource.ReturnIntHandler(eventSource_ReturnIntEvent);
  }

  private void eventSource_ReturnIntEvent
    (object sender, IntEventArgs eventArgs)
  {
    Console.Out.WriteLine("[Handler (" + this._id + ")] "
      + "Handling event, setting Data to " + this._id + ".");
    eventArgs.Data = this._id; 
  }
}

This code produces the following output. Note that the final value of the eventArgs.Data parameter (3) matches the value set by the last event handler method to handle the event.

[EventSource] Firing event...
[Handler (1)] Handling event, setting data to 1.
[Handler (2)] Handling event, setting data to 2.
[Handler (3)] Handling event, setting data to 3.
[EventSource] Finished firing event. eventArgs.Data: 3

1 comment:

  1. I really appreciate your efforts. Events in C# are too rigid that certaing things we can do very easily but certain things need to workout a lot.
    This is one of the examples. The main problem with C# Developers is they only look from microsoft point of view not from their own flow thought

    ReplyDelete

Hi spammers! No need to waste your time here; comments are heavily moderated, so if you like, you can save us both a little time and just move on to the next site. :-)

For everyone else: Thanks for visiting! Your comments are more than welcome!