Introduction
So, here we are at the final part of this series on delegates; and it wouldn't be complete without looking at one of the key uses of delegates in .NET: Event handling.
A Simple Timer
In this article, we are going to look at a simple timer example. This timer raises two events: one when the timer is started and another when the timer has elapsed. The complete code for the timer is as follows:
// C#
public delegate void TimerEventHandler(object sender, EventArgs eventArgs);
public class SimpleTimer
{
private TimeSpan time;
public event TimerEventHandler Started;
public event TimerEventHandler Elapsed;
public SimpleTimer(long milliseconds)
: this(TimeSpan.FromMilliseconds(milliseconds))
{
}
public SimpleTimer(TimeSpan time)
{
this.time = time;
}
public void Start()
{
Thread timerThread = new Thread
(
delegate()
{
OnStarted(new EventArgs());
Thread.Sleep(time);
OnElapsed(new EventArgs());
}
);
timerThread.Start();
}
private void OnStarted(EventArgs eventArgs)
{
if (Started != null)
Started(this, eventArgs);
}
private void OnElapsed(EventArgs eventArgs)
{
if (Elapsed != null)
Elapsed(this, eventArgs);
}
}
' Visual Basic
Public Delegate Sub TimerEventHandler(ByVal sender As Object, ByVal eventArgs As EventArgs)
Public Class SimpleTimer
Private _time As TimeSpan
Public Event Started As TimerEventHandler
Public Event Elapsed As TimerEventHandler
Public Sub New(ByVal milliseconds As Long)
Me.New(TimeSpan.FromMilliseconds(milliseconds))
End Sub
Public Sub New(ByVal time As TimeSpan)
_time = time
End Sub
Public Sub Start()
Dim timerThread As Thread = New Thread _
( _
Sub()
OnStarted(New EventArgs())
Thread.Sleep(_time)
OnElapsed(New EventArgs())
End Sub _
)
timerThread.Start()
End Sub
Private Sub OnStarted(ByVal eventArgs As EventArgs)
RaiseEvent Started(Me, New EventArgs())
End Sub
Private Sub OnElapsed(ByVal eventArgs As EventArgs)
RaiseEvent Elapsed(Me, New EventArgs())
End Sub
End Class
Now let's look at this code a little more closely. Firstly, notice how we declare a delegate for any methods which can be used to handle our two events:
// C#
public delegate void TimerEventHandler(object sender, EventArgs eventArgs);
' Visual Basic
Public Delegate Sub TimerEventHandler(ByVal sender As Object, ByVal eventArgs As EventArgs)
By convention, event handlers accept two parameters:
Parameter | Description |
---|---|
sender | The object which raised the event. |
eventArgs | An object of type EventArgs , or inherits from EventArgs , which contains encapsulates any optional data we wish to pass to our event handler |
We also declare the two events which out timer is going to raise:
// C#
public event TimerEventHandler Started;
public event TimerEventHandler Elapsed;
' Visual Basic
Public Event Started As TimerEventHandler
Public Event Elapsed As TimerEventHandler
Notice how the type of each of these events is that of the delegate which is going to handle them, in this case TimerEventHandler
.
We also have convenience methods for raising each of the events:
// C#
private void OnStarted(EventArgs eventArgs)
{
if (Started != null)
Started(this, eventArgs);
}
private void OnElapsed(EventArgs eventArgs)
{
if (Elapsed != null)
Elapsed(this, eventArgs);
}
' Visual Basic
Private Sub OnStarted(ByVal eventArgs As EventArgs)
RaiseEvent Started(Me, New EventArgs())
End Sub
Private Sub OnElapsed(ByVal eventArgs As EventArgs)
RaiseEvent Elapsed(Me, New EventArgs())
End Sub
Note how we fire the event in exactly the same way as we would call any other delegate. Additionally, in C#, we do a check to see if any handlers have been wired-up up to our event; as firing an event without any handlers will cause an exception to be thrown.
Finally, the code which actually runs our timer and raises the events. Note how the timer is executed in a separate thread and (simply because it is in-keeping with the theme of this series) is passed to the thread as an anonymous method:
// C#
public void Start()
{
Thread timerThread = new Thread
(
delegate()
{
OnStarted(new EventArgs());
Thread.Sleep(time);
OnElapsed(new EventArgs());
}
);
timerThread.Start();
}
' Visual Basic
Public Sub Start()
Dim timerThread As Thread = New Thread _
( _
Sub()
OnStarted(New EventArgs())
Thread.Sleep(_time)
OnElapsed(New EventArgs())
End Sub _
)
timerThread.Start()
End Sub
Using our Timer
The following code shows how to use our simple timer and hook up our own methods to handle the events it raises:
// C#
static void Main(string[] args)
{
SimpleTimer timer = new SimpleTimer(5000);
timer.Started += timer_Started;
timer.Elapsed += timer_Elapsed;
timer.Start();
Console.Read();
}
static void timer_Started(object sender, EventArgs e)
{
Console.WriteLine("Timer has started.");
}
static void timer_Elapsed(object sender, EventArgs e)
{
Console.WriteLine("Timer has elapsed.");
}
' Visual Basic
Sub Main(ByVal args() As String)
Dim timer As SimpleTimer = New SimpleTimer(5000)
AddHandler timer.Started, AddressOf timer_Started
AddHandler timer.Elapsed, AddressOf timer_Elapsed
timer.Start()
Console.Read()
End Sub
Private Sub timer_Started(sender As Object, eventArgs As EventArgs)
Console.WriteLine("Timer has started.")
End Sub
Private Sub timer_Elapsed(sender As Object, eventArgs As EventArgs)
Console.WriteLine("Timer has elapsed.")
End Sub
Note how the syntax for wiring up our event handlers to the events is slightly different from what we've seen previously. This is because events are a type of multicast delegate, and as such multiple handlers can be wired up the same event.
Summary
Handling of events is one of the key uses for delegates in .NET. All event handlers follow a specific convention in terms of their signature and multiple different handlers can be wired up to the same event.
Would you please explain lines 5-6 in the last listing?
ReplyDeletetimer.Started += timer_Started;
timer.Elapsed += timer_Elapsed;
I don't know C# well enough to understand this. It looks like events are being updated with the return values from functions which return void. Huh?
Yes, I agree the C# syntax is somewhat confusing. I guess the += operator was chosen to try and emphasise the fact that you are adding a handler to an event (which may already have other handlers wired up to it), rather than (re)assigning the handler.
ReplyDeleteIn that respect the VB.NET syntax is much more intuitive in my opinion.