posted on Sunday, November 07, 2004 6:46 PM
by
taylorza
Of Delegates and Events
I have often read and heard explanations of the differences between a Delegate and Events in the .NET framework; I thought I would try my hand at explaining the relationship between delegates and events. I will not be addressing all the finer details; I will purposely ignore most of the metadata related to events. I will only describe as much as I require conveying the relationship between delegates and events. Please do not expect this to be an exhaustive review of how to work with delegates and events or for that matter how they work.
For the purposes of this explanation I will use C# and IL for the examples, but most aspects should be relevant for VB.NET or any other language targeting the .NET Framework that exposes the .NET event mechanism.
The delegate declaration defines a new data type; instances of this data type can maintain a list of zero or more methods; each method in the list must have a signature that matches the delegate data type declaration. Take the following declaration for example
public
delegate void ValueChangedDelegate(object sender, EventArgs e);
This declares a new delegate type called ValueChangedDelegate. The type definition also defines the signature of the method that can be added to instances of this type. In this example the method must return void (function Sub in VB.NET) and accept object and EventArgs parameters.
The next code snippet declares a class called Foo that exposes a delegate as an instance variable called ValueHasChanged.
public
class Foo
{
public ValueChangedDelegate ValueHasChanged;
public void ChangeTheValue()
{
if (ValueHasChanged != null)
ValueHasChanged(null, EventArgs.Empty);
}
}
The above class pretty much addresses everything we would need to put together an event mechanism. We can add handlers to the delegate instance and at the appropriate time we can invoke the delegate to fire the handlers. In the sample code the event is fired when ever the method ChangeTheValue is called, of course in a real application it would do more that just invoke the delegate and in we would need to take extra precautions to prevent race conditions.
The following snippet demonstrates subscribing to our new "event" (note the quotes).
static
void Main()
{
Foo myFoo = new Foo();
myFoo.ValueHasChanged = new ValueChangedDelegate(OnValueChanged);
}
static
void OnValueChanged(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("It fired!!!!");
}
Our Foo class is instantiated and the reference stored in the variable myFoo, following that we create an instance of our delegate passing a method to the constructor; I will refer to this method as the handler method. The method OnValueChanged’s signature matches that required by our delegate. The newly created delegate instance is then assigned to the ValueHasChanged instance variable of the myFoo instance. Now when ever the delegate myFoo.ValueHasChanged is invoked the static method OnValueChanged will be called. Note that even though I have used static methods here neither events or delegates are limited to using static methods. At this point potential subscribers are free to add there handler functions to the myFoo.ValueChangedDelegate and these will be called when ever the delegate is invoked.
What has been described thus far addresses what we need to create and handle events. And in terms of pure functionality delegates do fit the bill here. The problem with we have so far is with the accessibility of the delegate, as it stands anybody using and instance of the Foo class can subscribe to the "event", but also just as easily invoke the delegate directly artificially firing the all handlers into action. The following code shows this.
Foo myFoo =
new Foo();
myFoo.ValueHasChanged = new ValueChangedDelegate(new ValueChangedDelegate(OnValueChanged));
// Even though the value has not change I am going to trick everyone
// into thinking it has by firing the event
if (myFoo.ValueHasChanged != null)
myFoo.ValueHasChanged(null, EventArgs.Empty);
Clearly we would not want our "event" fired at any arbitrary time, so we go to the drawing board and think through the problem and come up with the following solution.
public
class Foo
{
private ValueChangedDelegate _valueHasChanged;
public void AddValueChangedHandler(ValueChangedDelegate handler)
{
_valueHasChanged += handler;
}
public void RemoveValueChangedHandler(ValueChangedDelegate handler)
{
_valueHasChanged -= handler;
}
public void ChangeTheValue()
{
if (_valueHasChanged != null)
_valueHasChanged(null, EventArgs.Empty);
}
}
This solution does not look to bad, it can be improved, but the goal has been achieved we limit the access to the delegate while still allowing subscribers to subscribe and unsubscribe, while only the class itself can actually fire the event. The two problems with this solution is the amount of code we have to write. And when we make improvements to what we have, and we can/should make some, we need to go address all our hand rolled code, especially problematic after we have defined a number of delegates in many classes. We have also lost our ability to use the += and -= operator to subscribe and unsubscribe.
This is where events come into the picture; a .NET event formalizes and improves on the above pattern with the advantage of less typing and a nicer syntax for adding and removing handler functions. The following class definition uses the event modifier (yes modifier) to expose the delegate.
public
class Foo2
{
public event ValueChangedDelegate ValueHasChanged;
public void ChangeTheValue()
{
if (ValueHasChanged != null)
ValueHasChanged(null, EventArgs.Empty);
}
}
The new version of our class Foo2 now exposes the ValueHasChanged delegate as an event. This effect of this is that the compiler emits code that is very similar to the code we generated by hand earlier.
If you use ILDASM to inspect the code you will see that a private member has been declared.
.field private class WindowsApplication1.ValueChangedDelegate ValueHasChanged
This declaration is followed by the two compiler generated methods add_ValueHasChanged and remove_ValueHasChanged. These members correspond to our earlier solutions methods, AddValueChangedHandler and RemoveValueChangedHandler. The following is the IL declaration for the add_ValueHasChanged and remove_ValueHasChanged method.
.method public hidebysig specialname instance void add_ValueHasChanged (class Sample.ValueChangedDelegate 'value') cil managed synchronized
.method public hidebysig specialname instance void remove_ValueChangedAsEvent(class WindowsApplication1.ValueChangedDelegate 'value') cil managed synchronized
I am not going to delve into the rest of the metadata that is emitted for events, but it should be clear that by adding the event modifier to the delegate member we have modified how the delegate is exposed to users of the class just like with our hand rolled version, with two added benefits
·
Less coding
·
More consistent syntax for subscribing to events
·
Thread safe subscription to events
The second and last points are support added by the compiler, whereby the subscriber can use the += operator to subscribe to the event and the compiler will resolve this to a call to add_ValueHasChanged and -= will resolve to remove_ValueHasChanged. As for thread safety, calling the add_ValueHasChanged and remove_ValueHasChanged methods is done in a thread safe manner. Look at the IL declarations for these methods you will notice the synchronized flag, this flag specifies that the method can only be called from one thread at a time, still more functionality that we would have to add to our hand rolled solution.
In summary I leave you with the following:
Events are not like delegates, they ARE delegates. The event modifier defines how these delegates are published to subscribers.