RSS

ASP.NET event delegation

31 May

Did you ever wonder what is going on behind the scene of ASP.NET events? When you add an onclick event to, say, button how and when is this event executed?

If you drop a button control on your form and double click on it, .NET framework automatically generates both HTML presentation of the button and its OnClick event handler. The .aspx and .cs files will be updated with the following lines of code

//.aspx file


//code behind .cs file
protected void Button1_Click(object sender, EventArgs e){    }

When the button is clicked, how does .NET know what method to call? Obviously it’s specified in OnClick attribute of the button. Yes, but it doesn’t make it any clearer how the whole mechanism works. To make developers life easier, .NET framework hides the details of implementation.

Look at the following example.

Button1.Click += new EventHandler(Button1_Click);

This code does absolutely the same as OnClick attribute we saw before – it assigns an event handler to the button control. The only difference is that the last approach helps to keep presentation separate from implementation. The best place to add this code to is Page_Init method, which is called when the server loads the page.

You could ask why we didn’t assign the event handler directly like this:

Button1.Click = Button1_Click;

Actually this code has even two mistakes. First, C# language doesn’t have function pointers as C or C++ does. Instead C# provides us with delegates.

Delegates

So what is a delegate? As I have already mentioned, a delegate in C# is similar to a function pointer in C or C++. But unlike function pointers, delegate is a class that can hold a reference to a method. Unlike other classes, a delegate class has a signature, and it can hold references only to methods that match its signature. As this match is checked by compiler, delegates are type-safe and secure.

Another difference between delegates and function pointers is that a function pointer can only reference static functions, whereas a delegate can reference both static and instance methods. When the delegate references an instance method, it stores a reference to the method entry point and also a reference to an object instance on which to invoke this method (target object). For static methods only reference to a method is stored.

In the previous example

Button1.Click += new EventHandler(Button1_Click); 

EventHandler class is a delegate that encapsulates Button1_Click method. The signature of System.EventHandler class specifies that the methods it encapsulates must return void and take two parameters (by convention event delegates in the .NET have two parameters, the source that raised the event and the data for the event). So in our example we created a new EventHandler object containing Button1_Click method and added it to button’s Click event.

If you look at the code ASP.NET automatically generated for Button1_Click in .cs file

protected void Button1_Click(object sender, EventArgs e){    }  

you will notice it has exactly the same signature as EventHandler class defines.

ASP.NET controls use delegate of type System.EventHandler, but it’s possible to define your own custom type delegate. MSDN explains when custom delegates as useful:

Custom event delegates are needed only when an event generates event data. Many events, including some user-interface events such as mouse clicks, do not generate event data. In such situations, the event delegate provided in the class library for the no-data event, System.EventHandler, is adequate.

I will show how to create and use custom type delegates later in this post.

MultiCast delegates

If you remember I said there were two mistakes in that code in the beginning of this article. The second mistake is that all delegates that we use in ASP.NET as so-called MultiCast delegates. What is the difference?

A single delegate can invoke several methods at a time with the return type viod. Such delegates are called Multicast delegates. Multicast delegates inherit from System.MulticastDelegate class, rather than System.Delegate.

You may subscribe as many methods as you want to an event. Say, you want several objects to “know” that checkbox was checked/unchecked. All you need is to subscribe these objects to checkbox CheckedChanged event.

If you look at that code again you will see that we used “=” operator for adding a delegate. This will cause a compilation error. As we already know all delegates we can create in ASP.NET are MultiCast, that’s why “+=” operator must be used instead of “=”. It’s also possible to remove event handler with “-=” operator.

Note, when invoking MultiCast delegates if any of the encapsulated methods throws an exception it will prohibit the pending methods from invocation.
A way around this is to explicitly iterate the delegates and invoke them inside a try-catch block. To get the list of all assigned delegates we will use GetInvocationList method. What is the invocation list? MSDN gives a complete and easy to understand description

The invocation list of a delegate is an ordered set of delegates in which each element of the list invokes exactly one of the methods invoked by the delegate. An invocation list can contain duplicate methods. During an invocation, a delegate invokes methods in the order in which they appear in the invocation list. A delegate attempts to invoke every method in its invocation list; duplicates are invoked once for each time they appear in the invocation list. Delegates are immutable; once created, the invocation list of a delegate does not change.

So the code to execute all delegates in the invocation list will look like this:

foreach (myDelegateType dgt in myEventHandler.GetInvocationList()){
    try {
        dgt (null EventArgs.Empty));
    }
    catch{ } 
}

Custom events

Now when we know how delegates work we can try to create our own custom events.

Event functionality is provided by three interrelated elements:

  • a class that provides event data
  • an event delegate
  • the class that raises the event

The object that raises an event is called the event sender (Button in previous examples). The object that captures the event and responds to it is called the event receiver (Page).

The ASP.NET has a convention for naming classes and methods related to events.

  • Class that holds the data that is sent to event handler should have a name EventNameEventArgs. For example, WarningEventArgs. This class must derive from System.EventArgs;
  • A delegate called EventNameEventHandler. We have already seen this in the previous examples – EventHandler class;
  • Event defined in Sender with the name EventNameEventHandler;
  • A method named OnEventName that raises the event.

The last two items are hidden in the implementation of controls, that’s why we haven’t seen them before.

Event example

For better understanding let’s start coding. I will create a colony of nice ants (AntColony) and a colony of angry termites (TermitColony). Each time ant colony produces a new ant, termite colony will try to kill the baby. But they won’t attack unless the number of ants in the colony is more than three. Yeah, small colony 🙂

First we need to decide how we are going to let termites know that ant colony has created a new ant. We will raise an event and call a method of termite’s colony object. In this event we will pass the current number of ants and the reference to the newly created ant object.

To pass a custom parameter to the event I need to declare a class that will contain these parameters. This class derives from System.EventArgs. Accordingly to naming convention I will call it NotifyEventArgs. Here is the code:

class NotifyEventArgs:EventArgs {
        private int antCount;

        public int AntCount {
            get { return this.antCount; }
        }

        public NotifyEventArgs(int antCount) {
            this.antCount = antCount;
        }
}

The NotifyEventArgs class has a private field antCount that stores the count of ants in the colony, public AntCount property to make the value of antCount accessible and a constructor that sets antCount to the value passed as a parameter. Very simple.

Now let’s create an AntColony class. Mine looks the following way

class AntColony   {
    //number of ants in a colony
    private int antCount;
    
    public int AntCount{
            get { return antCount; }
            set { antCount = value; }
    }

    //method that Termites will call to kill a baby ant	
    public void beatAnt(){
            AntCount--;
    }
    
    //method to create a new ant
     public void bornAnt() { 
          AntCount++; 
          Console.WriteLine("Count of ants: " + AntCount); 
     }

     //When colony is created there it has one ant already
     public Ant() { AntCount++; }
}

Now add a TermiteColony class

class TermiteColony  {
     public Termites() { }
}

Very simple! Now let’s add the most interesting part – events!

First as we already know we need to declare a delegate. For our example we need a custom type delegate as we want to pass the count of ants as a parameter for the event.

Delegate should be declared outside of the class but in the same namespace as other classes. Otherwise you will have to specify the full name of delegate including the namespace. Here is my delegate called NotifyEventHandler.

public delegate void NotifyEventHandler (object sender, NotifyEventArgs e );

Note, the type of e parameter is a custom NotifyEventArgs class.

Now when we have a delegate, we need to add an event and event handler. Go back to AntColony and add a Notify event.

public event NotifyEventHandler Notify;

And a function that will invoke Notify event with parameters of NotifyEventArgs type. It should look like this

public event NotifyEventHandler Notify;

protected void onNotify(NotifyEventArgs antCount) { 
     if (Notify != null) Notify(this, antCount); 
}

The first line describes an event of NotifyEventHandler type. This means that all methods that this delegate will invoke must have the same signature as NotifyEventHandler.
The second line defines a method that invokes an event with parameters of a valid NotifyEventArgs type. It also checks if any method has subscribed to the event. If there is such a method then the event is invoked.

I want to add a method to TermiteColony that will kill a just born ant. If you remember this should only happen if the total count of ants is more than three. Have a look at the code:

public void WarNotify(object sender, NotifyEventArgs arg) {
    if (arg.AntCount > 3) BeatAnt((Ant)sender);
}

The WarNotify method will be called each time a new ant is born – it will run every time the event fires.

protected void BeatAnt(AntColony ant){
     ant.beatAnt();
     Console.WriteLine("I've killed the ant, Sir!");
}

This method gets a reference to AntColony object and calls its beatAnt method.

Job done! Add the following lines of code to your main execution class:

Ant antColony = new Ant();
            
Termites termiteColony = new Termites();
            
antColony.Notify += new NotifyEventHandler(termiteColony.WarNotify);

antColony.bornAnt();
antColony.bornAnt();
antColony.bornAnt();

And enjoy! You should see the following output when you run the program

Count of ants: 2
Count of ants: 3
Count of ants: 4
I've killed the ant, Sir!
Press any key to continue . . .

I hope this post helped you understand events and delegates in ASP.NET a little better.

P.S. source code can be found here.

Advertisements
 
1 Comment

Posted by on May 31, 2009 in ASP.NET

 

Tags: , ,

One response to “ASP.NET event delegation

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: