Introduction
Anyone who has used .NET events and delegates understands its power and ease of use. Creating an event and subscribing onto it has become quite simple in C# or VB.NET classes. Moreover, it is a helpful mechanism to implement (for instance) a responsive user interface. When carefully designed you don't need to poll for an object's state, instead you signal its state by means of an event.
But despite its ease of use, some functionality is still missing. Sometimes it is not only desired to listen to an event, but it might be useful to trigger the event a first time when you subscribe onto it. Standard .NET events only allow you to subscribe without knowing the initial state/value, but as long as the event isn't triggered, you cannot display it correctly. Typically you need to get (poll) the value first with a separate method or property to know the object's current state/value.
In this article I will describe in C# how you can add this extra event functionality to your classes. With little effort you can convert this to VB.NET as well.
At the end of the article, I will also show you how you can send an event from you C# class and how you can subscribe onto it from JavaScript without your class having to be an ActiveX control. In normal circumstances you can only subscribe to ActiveX control objects from JavaScript running from HTML or ASP pages.
Background
At first sight the C# delegate class looks like any other ordinary class. But as soon as you try to derive from it, to extend its base functionality, the C# compiler will emit an error that it's not allowed to derive from the delegate class. The same applies to events. Delegates and events are special compiler generated classes and are treated in a special way. The only solution is to create a new class where we can implement our new event code.
A Look at the Code
Situation 1: Standard Event
Let us first look how you would use normal events in C#. First we create the Clocks.Clock
class that contains the sample code like this.
private Clocks.Clock m_clock = new Clocks.Clock();
Next we subscribe to the standard C# TickEvent
m_clock.TickEvents += new Clocks.TickDelegate(Clock_Tick1);
where Clock_Tick1
is the function that will be called when the event is triggered. In order for the Clocks.Clock
class to trigger the standard event it now calls
if (TickEvent != null) TickEvent(ticks);
Situation 2: The Base ExtendedClockDelegate Class
As mentioned before we need to create a new delegate class to implement our new functionality. This is the ExtendedClockDelegate
class. The first thing we do is adding the same standard event mechanism. This will allow us to subscribe to the event as follows:
m_clock.ExtendedClockEvents.TickEvents += new Clocks.TickDelegate(
Clock_Tick2);
In order for the Clocks.Clock
class to trigger the extended event it now calls:
m_extendedClockEvents.TriggerEvent(ticks);
Situation 3: Align the Base ExtendedClockDelegate Class Event with the Standard Event
If you look carefully at both previous code samples, you notice that there is an extra "indirection" to subscribe to the event via the ExtendedClockDelegate
class. Can we somehow avoid this? The answer is yes. If we overload the operator +
and operator -
of the ExtendedClockDelegate
we can write again (as in situation 1).
m_clock.ExtendedClockEvents += new Clocks.TickDelegate(Clock_Tick3);
Situation 4: Extending the ExtendedClockDelegate Class More
So far, we have not really added a lot of new functionality. Let us now add the extra AddEventHandler
and RemoveEventHandler
methods. These methods allow us to subscribe to the ExtendedClockDelegate
class events, but we can now foresee extra method parameters that define how we want to subscribe. For instance, we can allow to subscribe to an event and have the event triggering our callback method immediately once we are subscribed. In our example we will send along the time value that was used when the last event was triggered. The code looks now as follows.
m_clock.ExtendedClockEvents.AddEventHandler(new Clocks.TickDelegate(
Clock_Tick4), Clocks.EventCondition.FireToCurrentSubscriber);
Situation 5: Variation on Situation 4
In our last example, we changed the AddEventHandler
and RemoveEventHandler
methods behaviour. Now we allow the subscriber code to define what the time value of the callback method should be when it triggers at the moment we subscribe. The code looks now as follows.
m_clock.ExtendedClockEvents.AddEventHandler(new Clocks.TickDelegate(
Clock_Tick5), Clocks.EventCondition.FireToCurrentSubscriber, 1234);
At the moment the subscription is finished, the callback method will be called instantly with the time value set to 1234.
Subscribing to C# Events from JavaScript
When running a JavaScript from HTML or ASP, it is only possible to subscribe to events when your object is a full ActiveX control, for example the button_onclick() event. But most of everyday classes are not controls. How can you subscribe then to an event from a non ActiveX control? Well, the Java runtime will translate the JavaScript callback function in a COM object with a default method (good old plain Automation feature). If you add a special Property to your C# class where you can set or get an object, you can assign the JavaScript callback method to your class Property (that I called ScriptCallbackObject
). How can the ExtendedClockDelegate
class know how to call the COM object's default method? The answer is: .NET's powerful feature reflection. Note that the C# classes involved need to be registered for COM interop, hence the "[System.Runtime.InteropServices.xxx]" attribute statements. The following code snippet shows how this is done.
From JavaScript code:
<script type="text/javascript" language="jscript">
...
var clock = new ActiveXObject("Clocks.clock");
var extendedClockEvents = clock.ExtendedClockEvents();
extendedClockEvents.ScriptCallbackObject = clock_Callback;
...
function clock_Callback(time)
{
document.getElementById("text_tag").innerHTML = time;
}
...
</script>
From C# code:
System.Type t = scriptCallbackObject.GetType();
t.InvokeMember("", System.Reflection.BindingFlags.InvokeMethod, null,
scriptCallbackObject, new object[] { time });
Note the empty string "" as the first parameter of InvokeMember
. This will execute the default method of the COM object without having to know its name. The sample package includes an HTML page that shows how you can do this.
About the Sample Code
The sample program contains a C# library with a Clocks.Clock
class. This class contains a standard .NET event and the Clocks.ExtendedClockDelegate
class. The latter class contains all extensions to the standard delegate. The clock class will trigger by default an event every second. Depending on the extra parameters of the AddEventHandler
or RemoveEventHandler
method, the event can be triggered additionally on your command.
The Clock executable program demonstrates the use of the Clocks.Clock
class. It will subscribe to its events in all possible ways.
The event.html file demonstrates how you can use C# events in JavaScript. Note that for this sample to work, you need to register clocks.dll for COM interop. The csproj file contains this command in the post build section.
Summary
I hope that this article has given you enough insight on how you can extend the standard C# (or VB.NET) event mechanism. The next picture shows the possibilities that are available in the source code. On top of this list you can also use the += and -= operator.
Of course, it's up to you, as a programmer, to extend its functionality even more.