Click here to Skip to main content
15,886,362 members
Articles / Programming Languages / C#

Complete Class of Events

Rate me:
Please Sign up or sign in to vote.
3.78/5 (6 votes)
27 Apr 2009CPOL5 min read 23.1K   24   5
Describing complete class of events and thread synchronization using add and remove accessors.

Introduction

We are going to discuss about the most familiar topic Events. Common Language Runtime (CLR) events are based on delegate type class members. Delegates are type safe to invoke callback methods. In callback methods, the object will receive the notification that it subscribed to. I am not going to talk about delegates. Let us move to events. We all know the button class that has an event called Click. When the button is clicked, the object registered with the events wants to receive the notification so that it will perform some action.

In this article, I have created the custom event program called TimeAlert. TimeAlert program gets the system time and checks whether it is scheduled. If it is scheduled, then it will display the message. And I will explain the complete class of event registration and unregistration using add and remove method and thread synchronization using add and remove accessors.

When the event is raised, the object raising the event wants to pass some additional information to the object receiving the event notification. Here we have created the custom EventArgs named TimeEventArgs. The information is encapsulated using the private and public readonly field. This has been created by inheriting the base class System.EventArgs. We have to suffix the word eventargs in the derived class name. In the below example, the public readonly variable (strMessage) holds the information.

Using the Code

C#
public class TimeEventArgs : EventArgs
{
    public string Message;

    public TimeEventArgs(string strMessage)
    {
        Message = strMessage;
    }
}

The implementation of System.EventArgs class in the .NET Framework Class Library is as shown below:

C#
[ComVisible(true)]
[Serializable]
public class EventArgs
{
    public static EventArgs Empty = new EventArgs();
    public EventArgs()
    { 
        
    }
}

If there is no need to pass any additional information to the receiver, then you can simply use the System.EventArgs rather than creating a new EventArgs object. Many events don't have additional information to pass on. For example, invoking the callback method is enough information for the button class. In such a case, there is no need to create a custom EventArgs object. We can simply say like the System.EventArgs serves as a base type for the other types to derive.

Declaring a Delegate to Handle Custom Event

We can simply say that delegate acts like an eventhandler. So that we can raise the events from anywhere in the application. In the below example, the delegate has two parameters, the first one is sender which holds object type and the second one is an custom eventargs. Many of the developers might have a doubt why this sender parameter should always be an object type. It is simple that the "sender" argument is the object that raised the event. We all know about the second parameter which holds some additional information. We have created the custom eventargs and a delegate. It is time to create the event and to raise it from the class so that the user can subscribe to it. When we raise the event, it will provide the sender information and the instance information.

C#
public class TimeAlarm
{
    public delegate void AlarmEventHandler(object Sender, TimeEventArgs e);

    public event AlarmEventHandler TimeEvent;        
}

Image 1

ILdasm.exe Displaying the Metadata Produced by the Compiler for the Event

The compiler will compile the below single line of code into three constructs:

C#
public event AlarmEventHandler TimeEvent;
  1. Private delegate field initialized to null:
    C#
    private  AlarmEventHandler TimeEvent = null;
  2. A public add_ TimeEvent method. This allows an object to register interest with an event:
    C#
    [MethodImpl(methodImplOptions.Synchronized)]
    public void add_TimeEvent( AlarmEventHandler value)
    {
           TimeEvent = (AlarmEventHandler) Delegate.Combine(TimeEvent,value);
    }
  3. A public add_ TimeEvent method. This allows an object to unregister interest with an event:
    C#
    [MethodImpl(methodImplOptions.Synchronized)]
    public void remove_TimeEvent( AlarmEventHandler value)
    {
           TimeEvent = (AlarmEventHandler) Delegate.Remove(TimeEvent,value);
    }

The compiler has declared that the private field in the first construct is a delegate type. This field refers to a list of delegates. When an event occurs, this field will be notified. Initially, this field is set to null meaning that no listeners have been registered in this event. When a method registers interest with an event, then this field refers to an instance. Whenever a listener registers interest in an event, the listener is simply adding an instance of the delegate type to the list. The compiler has generated this type as private because of its protection meaning that it may be mishandled outside of the class.

The second and third construct of C# compiler auto generated method is to register and unregister the object interest with an event. It uses the prefix “add_” and “remove_” for naming the methods. The register method uses the Delegate class and a static method Combine to add the instance of a delegate to the list of delegates and returns the list. To unregister an object, the method uses the Delegate class and static method Remove (instead of Combine) to remove the instance of a delegate to the list of delegates and returns the list. This is what happens internally when we are using the “+=” and “-=” to register/unregister with an event.

You can see the attribute [MethodImpl(methodImplOptions.Synchronized)] added in the second and third construct. Multiple listeners that can register/unregister with the event may corrupt the delegate list. But the above given attribute will synchronize that only one add or remove method can be used at any one time which is defined in the System.Runtime.CompilerServices namespace. Thread synchronization is required so that the list of delegate objects doesn't become corrupted.

We have used the MethodImpl attribute in an instance method. Now the CLR uses the object itself, the thread synchronization lock. Think if the class uses many events all the add and remove methods use the same lock. It will affect the scalability. If multiple threads use the same lock for registering and unregistering with the events, it will affect the performance. But it is a very rare scenario. The Thread synchronization guideline states that it should not take the lock on objects, because the object is exposed publicly. It will cause deadlock if we are running in a multi threading environment.

C#
private object eventlock = "";
public delegate void AlarmEventHandler(object Sender, TimeEventArgs e);
private AlarmEventHandler m_TimeEvent;
 public event AlarmEventHandler TimeEvent
{
    add 
    {
        lock (eventlock)
        {
            m_TimeEvent += value;
        }
    }
     remove
    {
        lock (eventlock)
        {
            m_TimeEvent -= value;
        }
    }
}

We have created the add and remove context explicitly instead of giving the burden to the compiler. Now see the first line. We have declared a private object which is used for thread synchronization lock. A private field named m_TimeEvent which holds the listener. We have extended the syntax after the event initialization. Within that we have added two accessors named add and remove which are mainly used to register and unregister the events. The main thing that we have ignored is the line [MethodImpl(methodImplOptions.Synchronized)] because our code needs proper thread synchronization lock.

C#
namespace CustomEvents
{
    class Program
    {   
        static void Main(string[] args)
        {
            TimeAlarm objTimeAlarm = new TimeAlarm();
            objTimeAlarm.TimeEvent += new TimeAlarm.AlarmEventHandler(CallAlarmMethod);

            Console.WriteLine("System time: " + System.DateTime.Now.ToString("HH.mm"));
            double dltime = Double.Parse(System.DateTime.Now.ToString("HH.mm"));
            objTimeAlarm.ActivateTimeAlarm(dltime);
            Console.ReadLine();
        }

        public static void CallAlarmMethod(object Sender, TimeEventArgs e)
        {
            Console.WriteLine(e.Message);            
        }
    }

    public class TimeEventArgs : EventArgs
    {
        public string Message;

        public TimeEventArgs(string strMessage)
        {
            Message = strMessage;
        }
    }

    public class TimeAlarm
    {

        private object eventlock = "";
        public delegate void AlarmEventHandler(object Sender, TimeEventArgs e);
        private AlarmEventHandler m_TimeEvent;

        public event AlarmEventHandler TimeEvent
        {
            add 
            {
                lock (eventlock)
                {
                    m_TimeEvent += value;
                }
            }

            remove
            {
                lock (eventlock)
                {
                    m_TimeEvent -= value;
                }
            }        
        }

        public void ActivateTimeAlarm(double dlTime)
        {
            string strMessage = string.Empty;
            if ((dlTime >= 10.30 && dlTime <=10.45))
                strMessage = "Break";
            else if (dlTime >= 13.00 && dlTime <= 14.00)
                strMessage = "Lunch";
            else if (dlTime >= 18.00)
                strMessage = "Fill your timesheet";
            else if (dlTime >= 9.30)
                strMessage = "Working";

            TimeEventArgs objEvent = new TimeEventArgs(strMessage);
            AlarmEventHandler temp = m_TimeEvent;
            temp(this, objEvent);
        }
    }
}

Output

Image 2

History

  • 27th April, 2009: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
India India
Working as a software engineer with 5+ Years of experience in Web Based applications using Microsoft Technologies.

Comments and Discussions

 
GeneralMy vote of 2 Pin
Obalix8-May-09 20:29
Obalix8-May-09 20:29 
GeneralMy vote of 1 Pin
Marek.T5-May-09 3:57
Marek.T5-May-09 3:57 
GeneralRe: My vote of 1 [modified] Pin
Karthikeyan Govindan6-May-09 0:40
Karthikeyan Govindan6-May-09 0:40 
GeneralExcellent work Pin
r_anand_kumar27-Apr-09 20:30
r_anand_kumar27-Apr-09 20:30 
GeneralExcellent presentation Pin
adatapost27-Apr-09 17:27
adatapost27-Apr-09 17:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.