Introduction to Event Driven Programming in C#
Join the DZone community and get the full member experience.
Join For FreeAt one point in our careers we've all been introduced to, or at least heard of, event driven programming, programming where the overall flow of the application is controlled by user generated events. In C# event driven programming is made much easier with the offering of Delegates & Events. So today we're going to look at event driven programming in C#. Before we get into the meat of event driven programming we need to set a couple definitions for terms we will be using in this article:
Delegate:
A delegate is very similar to a function pointer in C++. It is a reference type that encapsulates a method which has a specific signature and a return type.
Events:
An event allows a class (or other object) to send notifications to other classes (or objects) that something has occurred. In simple terms an event is the outcome of a specific action.
In the old days all programs were very linear, they followed a set of commands/paths and user interaction was merely an after-thought, or limited to filling in forms or providing data. Over time things have changed drastically, todays programs are more user driven than ever, and as developers we need to change & adapt to this. In todays environment we as developers know that actions are going to need to be taken, but at design-time we do not know what order these actions are going to occur, because it's all dependent on what actions the users take. We know we're going to have to execute an action, but until the user makes their action we dont know which method (or event) is supposed to be executed. That's where event driven programming comes into play. With events & delegates we can make these decisions on the fly.
So how exactly do delegates & events help with event driven programming you may ask? Well I have an answer. In event driven programmer you have a publisher, a class that exposes the event) and at least one subscriber, a class that subscribes to your event through the use of a delegate. So, given that context your publisher raises an event, more than likely through some user interaction or user choice, and your subscriber makes a decision on what to execute based on what event was raised, thus the term event driven programming.
To demonstrate the use of delegates & events in C# we're going to create an alarm clock application that is driven by events from user interaction. In our alarm clock we will have 2 classes that inherit from System.EventArgs, which will hold the data for our events. We will have SetAlarmEventArgs and AlarmTriggerEventArgs and here's the code for them
using System;namespace DZoneArticles.EventsAndDelegatesExample{ public class SetAlarmEventArgs : EventArgs { public DateTime SetTime { get; set; } public bool AlarmEnabled { get; set; } }}using System;namespace DZoneArticles.EventsAndDelegatesExample{ public class AlarmTriggerEventArgs : EventArgs { public bool AlarmTurnedOff { get; set; } }}
We will implement an interface based design into our example as well, we will name it IAlarmClock and it will handle the methods for working with the alarm clock that AlarmClock.cs will implement
using System;namespace DZoneArticles.EventsAndDelegatesExample{ public interface IAlarmClock { void AlarmClockSet(object sender, SetAlarmEventArgs e); void AlarmTurnedOff(object sender, AlarmTriggerEventArgs e); void TriggerAlarm(object sender, AlarmTriggerEventArgs e); }}
In our AlarmClock.cs we will implement the above interface, and have properties that will be accessible in AlarmClockService.cs, which will do all the controlling of our alarm clock example
using System;namespace DZoneArticles.EventsAndDelegatesExample{ public class AlarmClock : IAlarmClock { public DateTime AlarmSetTime { get; set; } public bool AlarmEnabled { get; set; } public bool HitSnooze { get; set; } public int SnoozeTime { get; set; } public AlarmClock(DateTime setValue, bool enabled, bool snooze) { this.AlarmSetTime = setValue; this.AlarmEnabled = enabled; this.HitSnooze = snooze; } void IAlarmClock.AlarmClockSet(object sender, SetAlarmEventArgs e) { e.AlarmEnabled = this.AlarmEnabled; } void IAlarmClock.TriggerAlarm(object sender, AlarmTriggerEventArgs e) { //put your code here you want executed when the alarm is triggered\ e.AlarmTurnedOff = this.AlarmEnabled; } void IAlarmClock.AlarmTurnedOff(object sender, AlarmTriggerEventArgs e) { e.AlarmTurnedOff = true; } }}
Now for our alarm clock service. This is where all the events will be handled and triggered. We have two delegates and events derived from these delegates
public delegate void AlarmSetEventHandler(object sender, SetAlarmEventArgs e);public delegate void AlarmTriggerEvenHandler(object sender, AlarmTriggerEventArgs e);public event AlarmSetEventHandler AlarmSetEvent;public event AlarmTriggerEvenHandler AlarmTriggerEvent;
That will handle the meat of our event driven alarm clock example. We will ask the user for a value to set the alarm clock to, then monitor this with a timer in our client application. Once the proper date & time are reached we will call our TriggerAlarm method, which will then set out AlarmTriggerEvent. Once the user gives the command to turn the alarm off we will unregister the AlarmTriggerEvent to shut the alarm down, here's the code for our AlarmClockService.cs class
using System;namespace DZoneArticles.EventsAndDelegatesExample{ public class AlarmClockService { public delegate void AlarmSetEventHandler(object sender, SetAlarmEventArgs e); public delegate void AlarmTriggerEvenHandler(object sender, AlarmTriggerEventArgs e); public event AlarmSetEventHandler AlarmSetEvent; public event AlarmTriggerEvenHandler AlarmTriggerEvent; private AlarmClock clock { get; set; } public bool IsAlarmTriggered { get; set; } public bool IsAlarmEnabled { get; set; } public AlarmClockService(AlarmClock c) { this.clock = c; } public void SetAlarm(DateTime setValue) { try { if (clock == null) { throw new ArgumentException("An AlarmClock instance much be provided"); } else { if (DateTime.Compare(setValue.Date, System.DateTime.Now.Date) >= 0 && (!(TimeSpan.Parse(setValue.ToShortTimeString()) < System.DateTime.Now.TimeOfDay))) { if(this.IsAlarmTriggered) OnAlarmTimeSet(setValue); } else { throw new Exception("A date & time value before the current date & time isnt valid"); } } } catch (Exception ex) { Console.WriteLine(ex.Message); } } protected void OnAlarmTimeSet(DateTime setValue) { if (AlarmSetEvent != null) { SetAlarmEventArgs args = new SetAlarmEventArgs(); args.SetTime = setValue; args.AlarmEnabled = true; AlarmSetEvent(this, args); } } public void TriggerAlarm() { while ((clock.AlarmEnabled) && (!clock.HitSnooze)) { OnAlarmTrigger(); } } protected void OnAlarmTrigger() { if (AlarmTriggerEvent != null) { AlarmTriggerEventArgs args = new AlarmTriggerEventArgs(); args.AlarmTurnedOff = false; //code to play your alarm sound goes here AlarmTriggerEvent(this, args); } } public void CancelAlarm(IAlarmClock iclock) { //make sure it's been cancelled if (!this.IsAlarmEnabled) this.AlarmTriggerEvent -= new AlarmTriggerEvenHandler(iclock.AlarmTurnedOff); //code to stop playing the alarm sound } public void AttachSetEvent(IAlarmClock iclock) { this.AlarmSetEvent += new AlarmSetEventHandler(iclock.AlarmClockSet); } public void AttachTriggerEvent(IAlarmClock iclock) { this.AlarmTriggerEvent += new AlarmTriggerEvenHandler(iclock.TriggerAlarm); } }}
Now that we have the layout completed let's look at a quick implementation. We will need a timer class to monitor the time once the user sets the alarm. Since the actual timer control isnt available in a console application (which we are using for this example) we'll have to use the System.Threading.Timer class. First we need to create a callback method which matches the callback delegate (this will be used in the constructor of our timer control). In here we will set the value of a global DateTime variables being used to determine whether we trigger the alarm
private static void CheckTime(Object state){ nowValue = DateTime.Now;}
Then in main we ask the user for an alarm value, we then tick each second looking for the proper value and once we reach it we trigger the alarm. Once the alarm is triggered we give the user an option to turn the alarm off then act accordingly:
private static DateTime nowValue;static void Main(string[] args){ Console.WriteLine("Please provide a time for your alarm to go off"); DateTime setValue; if (!DateTime.TryParse(Console.ReadLine(), out setValue)) { Console.WriteLine("Invalid entry"); } else { //create a new stock isntance IAlarmClock c = new AlarmClock(setValue, true, false); //provide our stock market instance know the stock AlarmClockService service = new AlarmClockService((AlarmClock)c); //attach events to the stock market, so it can notify the investor //this uses the Observe pattern with delegates & events service.AttachSetEvent(c); service.AttachTriggerEvent(c); //start the stock market service.SetAlarm(); TimerCallback call = new TimerCallback(CheckTime); //create a timer that ticks every second Timer timer = new Timer(call, null, 0, 1000); for (; ; ) { if (nowValue == service.triggerValue) { service.TriggerAlarm(); break; } } Console.WriteLine("Press C to cancel alarm"); if (Console.ReadLine().ToLower() == "c") { service.IsAlarmEnabled = false; service.CancelAlarm(c); } } Console.ReadKey();}
So that's how event driven programming works. I know this is a very simple example but I was trying to introduce you to the concept of event driven programming, and I know we could have made this more robust, such as snooze and such, but as stated this is just an example on how event driven programming works. I also want to admit that this implementation probably isnt the most efficient way to do it, and maybe in the future I'll have an addition to this article with a more improved, robust and efficient example on an alarm clock application with event driven programming. Thanks for reading :)
Opinions expressed by DZone contributors are their own.
Comments