Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Delegates And Events - The Uncensored Story - Part 1

0.00/5 (No votes)
19 Nov 2000 1  
This is a part of a series of articles that aims at fully understanding delegates and events
  • Download source files - 1.15 Kb
  • Introduction

    All of us have been exposed to event driven programming of some sort or the other. C# adds on value to the often mentioned world of event driven programming by adding support through events and delegates. This article is a part of a series that aims at understanding fully the way in which delegates and events operate. Part1 helps you understand the role of multicast delegates in the context of UI interaction. The emphasis of this article would be to identify what exactly happens when you add an event handler to your common UI controls. A simple simulation of what could possibly be going on behind the scenes when the AddOnClick or any similar event is added to the Button class will be explained. This will help you understand better the nature of event handling using multi cast delegates. All the examples provided are in C#.

    Events

    An event is the outcome of an action. There are two important terms with respect to events. The event source and the event receiver. The object that raises the event is called event source and the object that responds to the event is called event receiver. Did you observe a hitch in the above explanation? O.K. my event source raises the event and my event receiver receives it, but what is the communication channel between them. Take the instance of you talking with a friend of yours over the telephone. Now you are the event source and your friend is the event receiver and the telephone cable is the communication channel. Similarly the communication channel between an event source and an event receiver is the delegate. The internals of events and their relationship to delegates would be dealt with in a later article. The explanation of events here is just to help the reader understand and visualize what exactly events are.

    Delegates

    Forget computers for a moment. Just think about delegates in the real world. Who are delegates? Delegates are people who represent a particular country or region or state in another country or region or state. Extend this same definition now to C#. In C#, delegates act as an intermediary between an event source and an event destination.

    To be even precise delegates are similar to function pointers. They can be called as type safe function pointers. Delegates have other uses in addition to event handling. But this article would focus on describing delegates with respect to event handling. A delegate class used as an intermediary between an event source and event receiver is called an event handler.

    Types of Delegates

    There are basically two types of delegates. Single Cast delegate and Multi Cast delegate. Let us first understand at the broader level the definition of both these delegates. A single cast delegate can call only one function. A multi cast delegate is one that can be part of a linked list. The multi cast delegate points to the head of such a linked list. This means that when the multi cast delegate is invoked it can call all the functions that form a part of the linked list. Assume that i have several clients who would like to receive notification when a particular event occurs. Putting all of them in a multi cast delegate can help call all the clients when a particular event occurs.

    To support a single cast delegate the base class library includes a special class type called System.Delegate. To support multi cast delegates the base class library includes a special class type called SystemMultiCastDelegate.

    Single Cast delegate

    The signature of a single cast delegate is shown below. The letters in italics can be replaced with your own names and parameters.

    public delegate Boolean  DelegateName (parm1, parm2)

    When the compiler compiles the statement above, it internally generates a new class type. This class is called DelegateName and derives from System.Delegate. Just to make sure just check the ILDisassembler code to check that this is happening.

    As an example let us create a single cast delegate named MyDelegate which would point to a function MyFunction. The code appears as below,

    public delegate Boolean MyDelegate(Object sendingobj, Int32 x);
    
    public class TestDelegateClass
    {     
    	Boolean MyFunction(Object sendingobj, Int32 x) 
    	{           
    		//Perform some processing           
    
    		return (true);  
    	}
    	       
    	public static void main(String [] args)
    	{
    		//Instantiate the delegate passing the method to invoke in its constructor
    
    		MyDelegate mdg = new MyDelegate(MyFunction);
                
    		// Construct an instance of this class
    
    		TestDelegateClass tdc = new TestDelegateClass();
                   
    		// The following line will call MyFunction
    
    		Boolean f = mdg(this, 1);
           
    	}
    }
    

    How does the above code work?

    The System.Delegate contains a few fields. The most important of them are Target and Method.

    The Target field identifies an object context. In the scenario above this would point to the TestDelegateClass being created. If the method to be invoked is a static method then this field will be null.

    The Method field identifies the method to be called. This field always has some value. It is never null.

    To the intelligent reader, if you observe the IL Disassembler code you would see that the constructor for MyDelegate contains two parameters. But in our case we are passing only one parameter? Any guesses on how this is done? Yeah you are right! It gets done internally by the compiler. The compiler resolves the call above to a call passing in the two parameters required. If you observe in C++ with managed extension, the object and the address of the function have to be passed explicitly. But in VB and C# we have done away with this with the compiler filling in the necessary details.

    In the code sample above we saw,

    Boolean f = mdg(this, 1);

    When the compiler comes across this line, this is what happens,

    1. The compiler identifies that mdg is a delegate object.
    2. The delegate object has the target and method fields as described above.
    3. The compiler generates code that calls the Invoke method of the System.Delegate derived class i.e MyDelegate.
    4. The Invoke method internally uses the MethodInfo type identified by the delegate's Method field and calls the MethoidInfo's invoke method.
    5. The MethodInfo's invoke method is passed the delegates Target field which identifies the method and an array of variants which contains the parameters to the method to be called. In our case, the array of variants would be an Object and an Int32 value.

    The above discussion would have made you clear about what happens internally when a method gets called through a Single Cast delegate.

    Multi Cast delegate

    The signature of a mutli cast delegate is shown below. The letters in italics can be replaced with your own names and parameters.

    public delegate void DelegateName (parm1, parm2)

    Whatever has been explained with respect to Single cast delegate holds good even in the context of a Multi Cast Delegate. There is a small addition here. Since a multi cast delegate represents a linked list, there is an additional field called prev which refers to another Multi Cast Delegate. This is how the linked list is maintained.

    The return type if you have observed has changed from Boolean to void. Have you guessed the reason as to why it happens? The reason is that since several multi cast delegates get called consecutively we cannot wait to get the return value from each of these methods being called.

    The code shown in the example for single cast delegate will work in the case of multi cast delegate too. The only difference is that the delegate signature needs to be changed, the method signature needs to be changed to return void instead of Boolean.

    The power of multi cast delegates come in when you combine delegates to form a linked list. The Combine method is available in the System.Delegate class as a static method. The function signature as follows.

    public static Delegate Combine(Delegate a, Delegate b);

    What the above method does is that it combines the two delegates a and b and makes b's prev field point to a. It returns the head of the linked list. This in turn has to be type casted to our delegate type.

    Similar to the Combine method we have a remove method which removes a delegate from the linked list and gives you the modifies smaller linked list with a pointer to the head. The signature of the remove method is as follows,

    public static Delegate Remove(Delegate source, Delegate value);

    Here's a small sample that illustrates the use of a multi cast delegate,

    using System;
    
    class MCD1
    {
        public void dispMCD1(string s)
        {
            Console.WriteLine("MCD1");
        }
    }
    
    class MCD2
    {
    
        public void dispMCD2(string s)
        {
            Console.WriteLine("MCD2");
        }
    }
    
     
    
    public delegate void OnMsgArrived(string s);
    
    class TestMultiCastUsingDelegates
    {
        public static void Main(string [] args)
        {
            MCD1 mcd1=new MCD1();
            MCD2 mcd2=new MCD2();
            // Create a delegate to point to dispMCD1 of mcd1 object
    
            OnMsgArrived oma=new OnMsgArrived(mcd1.dispMCD1);
    
            // Create a delegate to point to dispMCD2 of mcd2 object
    
            OnMsgArrived omb=new OnMsgArrived(mcd2.dispMCD2);
     
            OnMsgArrived omc;
            // Combine the two created delegates. Now omc would point to the head of a linked list
    
            // of delegates
    
            omc=(OnMsgArrived)Delegate.Combine(oma,omb);
     
            Delegate [] omd;
             // Obtain the array of delegate references by invoking GetInvocationList()
    
            omd=omc.GetInvocationList();
     
             OnMsgArrived ome;
            // Now navigate through the array and call each delegate which in turn would call each of the
    
            // methods
    
            for(int i=0;i<omd.Length;i++)
            {
                // Now call each of the delegates
    
                ome=(OnMsgArrived)omd[i];
                ome("string");
            }
        }
    }
    

    The examples in this article would revolve around Multi Cast Delegates. It is essential that you have understood the basics of single cast and multi cast delegates before you proceed further.

    Problem Scenario

    The code below shows an example of adding a button to a Form. The button is associated with an event handler that handles whatever happens when the button is pressed. When the form is displayed and the button is pressed a message box showing "Button Clicked" will appear.

    Compilation directions: Compile using

    csc /r:System.DLL;System.WinForms.DLL;Microsoft.Win32.Interop.DLL FormTest.cs
    /*
    Creating a form and adding a button to it and associating the button with an event handler
    */
    using System;
    using System.WinForms;
    public class FormTest : Form
    {
        //Create a button
    
        private Button button1 = new Button();
        public static void Main (string [] args)
        {
            //Run the application
    
            Application.Run(new FormTest());
        }
        
        public FormTest()
        {
            //Set up the Form
    
            this.Text = "Hello WinForms World";
            //Set up the button
    
            button1.Text = "Click Me!";
            //Register the event handler
    
            button1.AddOnClick(new System.EventHandler(buttonClicked));
            //Add the controls to the form
    
            this.Controls.Add(button1);
        }
     
        //The event handling method
    
        private void buttonClicked(object sender, EventArgs evArgs)
        {
            MessageBox.Show("Button clicked");
        }
    }
    

    The topic of our discussion would be the routing of calls between the time when the click event is raised and the time when the method displaying "Button clicked" is displayed.

    Components of the Exercise

    This section will talk about the components that would complete the architecture,

    1. Delegate Event Handler

    A delegate event handler that routes the call to the appropriate method. Let us call it ButtonEventHandler. The declaration of this delegate should follow that the same for declaration of multicast delegates. The declaration is as follows.

    public delegate void ButtonEventHandler(object sender, ButtonEventArgs e);

    Parameters

    sender - Identifies the origin of the event

    ButtonEventArgs - Identifies the Event Arguments. This class is derived from EventArgs. EventArgs is the base class for event data. The data with respect to the event generated is going to be held in this class. The reason why I have made use of a seperate class is that i am going to use a string member variable in the derived class which identifies the the action.

    2. EventArgs derived class

    The reason for using a derived class for EventArgs has been mentioned in the previous point. The class looks like this:

    public class ButtonEventArgs : EventArgs
    {      
    	public string msg; //identifies the action
    
    	public ButtonEventArgs(string message)
    	{
    		msg=message;  
    	}
    }

    3. A button class

    This class is the simulation of the NGWS SDK Button Class. A control class such as a button class must be able to generate events when actions such as clicking on it are performed. Now think logically, the button is pressed and a message indicating the same should be displayed. The button here is the event source. Forget for a moment about the event receiver. Now what is required in between? Yeah! you guessed it right, a delegate to forward the calls to the appropriate event receiver. Now if, you want to add a delegate to your button you do it through Add methods in your button class. Similarly if you want to remove a delegate you do it through a Remove method in your button class. These methods must be named AddOn<Event Name> and RemoveOn<Event Name> according to the NGWSSDK documentation. Our button class will respond to click and press events. Thus the methods in our class should be AddOnPress, RemoveOnPress, AddOnClick and RemoveOnClick. The signature of the above methods will be as follows,

    public void AddOnClick(ButtonEventHandler handler);
    public void RemoveOnClick(ButtonEventHandler handler);
    public void AddOnPress(ButtonEventHandler handler);
    public void RemoveOnPress(ButtonEventHandler handler);
    

    At the end of this we have added the methods which add and remove delegates corresponding to Click and Press events in the button class.

    Now what is required? The most important part, methods that raises the events. The naming conventions followed for such methods are On<Event Name>. Thus the methods in our class should be OnClick and OnPress. These methods are passed the event data. The method raises the events by invoking the delegates.

    The signature of the mehods will be as follows,

    protected virtual void OnPress(ButtonEventArgs e);
    protected virtual void OnClick(ButtonEventArgs e);

    Why are the above two methods declared as protected? To help the subclasses override this event without attaching a delegate to it.

    What happens in the SDK Button class. When the button is pressed, an event data gets created, the OnClick method gets called passing in the event data as the parameter. The OnClick method in turn calls the event receivers through the delegates. Do you observe a missing link above? If the OnClick method gets passed the event data, who creates the event data? The answer is that the event data could be created by the SDK. For our purposes since we cannot tap onto the SDK let us simulate what the SDK does by creating two methods whose only purpose is to create the event data and call the appropriate OnClick or OnPress methods. The signature of these methods is as follows,

    public void click();
    public void press();
    

    Now let us take a look at the whole button class,

    class button
    {
        private ButtonEventHandler beh=null; // For click
    
        private ButtonEventHandler bep=null; // For press
    
     
        public void AddOnClick(ButtonEventHandler handler)
        {
            Console.WriteLine("Adding click event handler");
            beh=(ButtonEventHandler)Delegate.Combine(beh, handler);
        }
     
        public void RemoveOnClick(ButtonEventHandler handler)
        {
            Console.WriteLine("Removing click event handler");
            beh=(ButtonEventHandler)Delegate.Remove(beh, handler);
        }
     
        protected virtual void OnClick(ButtonEventArgs e)
        {
            if (beh!=null)
            {
            beh(this, e);
            }
        }
     
        public void AddOnPress(ButtonEventHandler handler)
        {
            Console.WriteLine("Adding Press Event handler");
            bep=(ButtonEventHandler)Delegate.Combine(bep, handler);
        }
     
        public void RemoveOnPress(ButtonEventHandler handler)
        {
            Console.WriteLine("Removing Press Event handler");
            bep=(ButtonEventHandler)Delegate.Remove(bep, handler);
        }
     
        protected virtual void OnPress(ButtonEventArgs e)
        {
            if (bep!=null)
            {
            bep(this, e);
            }
        }
     
        public void click()
        {
            ButtonEventArgs bea=new ButtonEventArgs("clicked");
            OnClick(bea);
        }
     
        public void press()
        {
            ButtonEventArgs bea=new ButtonEventArgs("pressed");
            OnPress(bea);
        }
    }
    

    Now let us begin a line by line walkthrough of the above code,

    private ButtonEventHandler beh=null; // For click
    
    private ButtonEventHandler bep=null; // For press

    The above two lines declare two delegates. Both these delegates are multicast delegates. They represent the series of method calls that have to take place when an event occurs. They are initialized to null to indicate that they point nowhere when the class is first instantiated. I am using two delegates here because I assume that separate delegates are used to track separate events. In the code snippet above, the delegate beh would handle click calls and the delegate bep would handle press calls.

    public void AddOnClick(ButtonEventHandler handler)
    {
       Console.WriteLine("Adding click event handler");    
       beh=(ButtonEventHandler)Delegate.Combine(beh, handler);
    }

    The AddOn methods are used to add a list of methods that need to get called when an event occurs. Thus it is natural that it receives a delegate as a parameter which in turn points to a function. Depending on the number of methods that needs to be invoked when an event occurs, this method is invoked multiple times passing the appropriate delegate parameter. The situation can be better explained with the help of a figure,

    beh ------> ButtonEventHandler1 -----> &Fn1()                  
                ButtonEventHandler2 -----> &Fn2()
                ButtonEventHandler3 -----> &Fn3()
                ButtonEventHandler4 -----> &Fn4()

    As the figure above shows, the beh pointer points to a delegate which in turn points to a function. In the case of multicast delegate, there is a prev field which gets initialized to null when the delegate is constructed. Now when you invoke the combine method as shown in the code snippet above, the prev field of handler points to beh. Thus when many delegates are combined using a Combine method, what internally happens is that a linked list gets created. That is why when we invoke the delegate from the OnClick or OnPress methods we find that all the functions combined get called. This makes it possible for multiple clients to receive notification about an event. This is the funda behind multi cast delegates.

    The same explanation applies to RemoveOn methods.

    4. The Form Simulation

    In our form example explained in the beginning of the article we had a form class derived from Form which constructed a button and attached event handlers. We are going to simulate the same but the only difference would be that we would not be creating a GUI but would instead have a client which adds our simulated button class and attaches event handling methods to it. For a change, instead of explaining the source code in bits and pieces let me put in explanations in the source code itself.

    The code for the client class is as follows,

    public class DelegatesAndEvents
    {
        //Declare the simulated button class
    
        private button button1=new button();
     
        //Have a private constrcutor
    
        private DelegatesAndEvents()
        {
            /*
            Now start adding onclick and onpress events on the button class by passing the appropriate
            methods to which calls should be routed. What is being done below is that three methods
            are being added to the same OnClick event so that when the Click event is raised the
            delegate would call all the methods
            */
            button1.AddOnClick(new ButtonEventHandler(ClickEventHandler1));
            button1.AddOnClick(new ButtonEventHandler(ClickEventHandler2));
            button1.AddOnClick(new ButtonEventHandler(ClickEventHandler3));
            button1.AddOnPress(new ButtonEventHandler(PressEventHandler));
            button1.click();
            button1.press();
        }
     
        public static void Main(String [] args)
        {
            DelegatesAndEvents d=new DelegatesAndEvents();
        }
     
        public void ClickEventHandler1(object sender, ButtonEventArgs e)
        {
            Console.WriteLine("click event handler1");
            Console.WriteLine(e.msg);
            Console.WriteLine("after");
        }
     
        public void ClickEventHandler2(object sender, ButtonEventArgs e)
        {
            Console.WriteLine("click event handler2");
            Console.WriteLine(e.msg);
            Console.WriteLine("after");
        }
     
        public void ClickEventHandler3(object sender, ButtonEventArgs e)
        {
            Console.WriteLine("click event handler3");
            Console.WriteLine(e.msg);
            Console.WriteLine("after");
        }
     
        public void PressEventHandler(object sender, ButtonEventArgs e)
        {
            Console.WriteLine("press event handler");
            Console.WriteLine(e.msg);
            Console.WriteLine("after");
        }
    }

    Output

    Conclusion

    What goes on behind the scenes when event handlers are added? How do delegates aid in this? How can the event handling in a class be simulated, how do do multiple methods get called when a particular event has been raised etc. have been the core points of discussion dealt with in this article. I hope that the explanations would have helped in bringing about some amount of clarity. Some aspects are only internally known to the runtime and no effort has been made to dwelve into them. One such example is how does the click event get raised when the button is actually clicked. The article has focused only on what is absolutely necessary for us to understand the way delegates and events interrelate in the context of an event handler.

    License

    This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

    A list of licenses authors might use can be found here