Delegates to the Event - Part II
By Peter A. Bromberg, Ph.D.

Peter Bromberg

This is part two of a two part article covering C# Delegates and Events. If you would like to start at the beginning (recommended) go here. There will be a link to this article at the end of the first one. As we discussed in Part I, a delegate is a "type-safe, object oriented way to point to a method". In .NET, you can make a call on any method, either synchronously or asynchronously, by simply defining a delegate for the method and invoking the delegate.



An event, as one might guess, is a way that C# is used to signal that something has happened. The reason that events go hand in hand with delegates is because C# uses a delegate model for events. In the .NET Framework, the event model is composed of three parts:

  • The type that raises the event
  • A method that is called when the event is raised ("event handler"), and
  • A standardized way to connect the first two items.

In C#, the event handler need not be defined in the class that will generate the event. This provides us with both generality in the event model, and flexibility as well. In fact, event handlers are normally found in a different class than the one raising the event (For an example using MDI Windows Forms, see my recent article "MDI Inter-Form Communication with Events"). The "glue" that connects the event that is raised to the method that handles it is, of course, the delegate.

Delegates provide us with a generic reference to a method, matching it's specified return type and arguments. The delegate does not care what the method may do. A delegate is declared by using the C# keyword "delegate" and providing a method signature matching those methods to which the delegate can hold a reference:

public delegate void EventHandler ( object sender, EventArgs e);

The above example defines a delegate that can hold references to methods that do not have any return value, and that take two parameters: an object reference to the sender, and an instance of type EventArgs. If we wanted to create a method that would be an acceptable match to the above delegate, it might look like this:

public void Text_Changed(object sender, EventArgs e)
{
// your code here . . .
}

Our example above is also a sample of the predefined delegate, "EventHandler" in C#, which is designed to simplify commonly used programming needs. The object parameter means that any .NET type can be supplied, and the EventArgs parameter is an instance of the predefined EventArgs class, which is used whenever an event does not carry any event data. So, unless you have an event that will be providing specific event data, you would use this predefined delegate for your events. OK -- if we aren't sending in any event data, why do we need the second parameter? Well, the EventHandler delegate is used as a template for defining other delegates, and the EventArgs class is the base class for all custom event arguments classes. So, even though this "stock" event delegate doesn't accept any event data, we still need a "point of departure" for the OOP model to work effectively.

Define Your Event

In order to connect event handling methods to an event, we need to be able to define an event - so that interested objects can "subscribe" to be notified.:

public event EventHandler TextChanged;

The above defines an event named "TextChanged", using a delegate of type EventHandler. So now we have defined an event and the type of delegate which it uses, and all we need to do at this point is to raise the event. We do this by calling the event as if it were a method, passing in the arguments that match the delegate signature (object sender, EventArgs e):

Convention dictates that the raising of an event is done in a method named OnEventName. So here is an example of a method that would raise our TextChanged event:

protected void OnTextChanged()
  {
    if(TextChanged !=null)
      TextChanged(this, new EventArgs() );
 }

We need to first check that any observing methods have been attached to the delegate, hence the check to see if TextChanged is not null. Finally, we raise the event by calling it as we would a method, passing in "this" - a reference to the control or class to identify it as the sender of the event, and a new instance of the stock EventArgs class as the second parameter. At this point, our event, which has been defined as a delegate instance, calls all the methods that have been subscribed, with the arguments we supplied.

Eat Those Events

Now that we have our event and its delegate, we need to ensure that any object wanting to respond to events from the class adds an event handler for our event. This is done via the following syntax:

object.EventName+=new DelegateType(EventHandlerMethod);

We see this signature created by the Visual Studio.NET IDE and ASP.NET Designer all the time. For example,in any WebForm:

this.Load += new System.EventHandler(this.Page_Load);

For the TextChanged example, we could use the following code to add an event handler for the TextChanged event we've defined:

CustomTextBox1.TextChanged+=new EventHandler(CustomTextBox1_OnTextChanged);

public void CustomTextBox1_OnTextChanged(object sender, EventArgs e)
{
// your cool code here
}

Note that the method we have assigned to be the handler, "CustomTextBox1_OnTextChanged" has the same signature as the EventHandler delegate.

Convenntions

While I haven't been a 100% purist insofar as following accepted conventions, I think its important to do so, as it not only makes our code more readable to others; it also enables us to scan through our own code and more easily remember what it is doing after coming back to visit it after a period of time. The following table illustrates the accepted nomenclature for event - related patterns:

Event Context Convention Example
Delegate EventNameEventHandler TextChangedEventHandler
Event EventName TextChanged
Event raising method OnEventName OnTextChanged
Event Handler ObjectName_EventName CustomTextBox1_TextChanged

We could get a lot deeper into the event - handling process in C#.NET, but that's really a job for a chapter or even two or three chapters of a good book. My objective here in this two-part series has been to lay the groundwork for the very basics, and hopefully to make it that much easier for readers who need this information to be able to grasp it in an easily-digestible, simplified way.

 

Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform.