Generic event-handlers in .NET 2.0
I was writing-up an event handler with custom event
arguments, getting ready for defining the event args class along with a
delegate for the event definition (as you’re supposed to in .NET 1.1), when
I’ve noticed that in VS intellisense is helping me with an advice saying that I
can use a generic event argument. Cool !!!
So I’ve decided to take a couple of minutes off and see
what’s can I do with .NET 2.0.
In .NET 1.x, we had to do the following:
- Define
a delegate type that holds the signature for the even type.
- Define
a custom class that inherits from System.EventArgs.
- Define
a class that will raise the the event instance
- And
encapsulate an event of the delegate type in the class from #2.
- Attach
event handlers for the event
- And
then consume the event
Here’s a simple event definition compatible with MS.NET 1.1
(and still working in 2.0)
using System;
namespace
SofiaDev.Events.dn11
{
public delegate void CustomEventHandler(object sender, CustomEventArgs
e);
/// <summary>
/// Event Args class. Holds
data, specific for the event instance.
/// </summary>
public class CustomEventArgs : System.EventArgs
{
private DateTime
m_dtEventSend;
private string
m_strMessage;
/// <summary>
/// returns a message send
to the mesasge subscriber.
/// </summary>
public string Message
{
get { return
m_strMessage; }
}
/// <summary>
/// another specific field
for the event subscriber
/// </summary>
public DateTime
DateEventSend
{
get { return
m_dtEventSend; }
}
/// <summary>
/// Initializes a new
instance of the custom event arg instance
/// </summary>
public CustomEventArgs(string
message, DateTime dateEventSend)
{
m_strMessage
= message;
m_dtEventSend
= dateEventSend;
}
}
}
So that’s the event args definition and the delegate.
Then we need the class that does the incapsulation and
raises the event.
Like this:
using System;
using
System.ComponentModel;
using
System.Threading;
namespace
SofiaDev.Events.dn11
{
/// <summary>
/// Class that raises
events of the type we've defined in dn11_Event.cs
/// </summary>
public class SomeClassThatRaisesEvent
{
/// <summary>
/// This is the event
subscription hook. You should attach you're handler to it. When the moment is
right, the class will
/// call your handler with
the appropriate event args data.
/// </summary>
public event CustomEventHandler CustomEvent;
/// <summary>
/// This method actually
does the calling. If the event handler queue is not empty, it should call
/// the delegate, passing
the appropriate event arguments.
/// </summary>
/// <param name="e"></param>
protected virtual void OnCustomEvent(CustomEventArgs
e)
{
if ( null!=CustomEvent
)
{
CustomEvent(this, e);
}
}
/// <summary>
/// calls a method that
will return in 2 seconds
/// </summary>
public void
CallEventhandlersIn2Seconds()
{
BackgroundWorker bgw = new
BackgroundWorker();
bgw.DoWork
+= new DoWorkEventHandler(bgw_DoWork);
bgw.RunWorkerCompleted
+= new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
bgw.RunWorkerAsync();
}
/// <summary>
/// Actual work being done
here.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void bgw_DoWork(object
sender, DoWorkEventArgs e)
{
Thread.Sleep(2000);
}
/// <summary>
/// we'll call the event
handler from here.
/// </summary>
void bgw_RunWorkerCompleted(object
sender, RunWorkerCompletedEventArgs e)
{
string strMessage = string.Format("hello from the second thread at {0}", DateTime.Now.ToString("dd-MM-yyyy
hh:mm:ss"));
//init the custom event args instance with specific data
CustomEventArgs args = new
CustomEventArgs(strMessage, DateTime.Now);
//call our internal method that does the actual call of the
events on clients.
OnCustomEvent(args);
}
}
}
In this one, we have a couple of additional methods that
will help us demonstrate the event call itself. Check out the comments for
more info.
Then we can use the whole thing. Like this:
using System;
using
System.Collections.Generic;
using
System.Text;
using
System.Threading;
using
SofiaDev.Events.dn11;
namespace EventArgs
{
class Program
{
static bool
StillWaiting = true;
static void Main(string[] args)
{
//init the instance that will we're gonna be waiting on.
SomeClassThatRaisesEvent instance = new SomeClassThatRaisesEvent();
//attach an event handler of our own
instance.CustomEvent
+= new CustomEventHandler(instance_CustomEvent);
//call method that will call us in a couple of secs (when
he's ready
instance.CallEventhandlersIn2Seconds();
Console.WriteLine("about
to start waiting");
while ( StillWaiting )
{
Console.Write(".");
}
Console.WriteLine("\n\nExiting!!!");
}
static void
instance_CustomEvent(object sender, CustomEventArgs e)
{
Console.WriteLine("\nCall
returned. ");
Console.WriteLine("Message:
{0},\ndate: {1}", e.Message, e.DateEventSend.ToString());
StillWaiting
= false;
}
}
}
The problem here is that when you author an extensive
library, it takes a lot of time to do the same code over and over again.
The new thing in .NET 2.0, is the possibility to use a
generic delegate (there is one within the .NET BCL already) and use it
everywhere. To be able to do that, you only need to define your custom event
class holding the data specific to your event. In fact we can use the same class
declaration as in the .NET 1.1 example and still be able to use a generic
delegate as the event type.
Here’s how the same class will look with a generic event
declaration:
using System;
namespace
SofiaDev.Events
{
/// <summary>
/// same class as in the
.NET 1.1 example
/// </summary>
public class SomeClassThatRaisesEvent
{
/// <summary>
/// Only this time the
event handler will have a generic definition
/// </summary>
public EventHandler<CustomEventArgs> CustomEvent;
protected void OnCustomEvent(object sender, CustomEventArgs
e)
{
if (null !=
CustomEvent)
CustomEvent(this, e);
}
}
}
An example solution with both ways of doing the event
declaration is available as an attachment.