|Multicast Delegates and Events|
|Written by Mike James|
|Friday, 24 June 2016|
Page 2 of 3
An Arithmetic of Delegates
You may well know the += operator for adding to the invocation list but there is also a -= operator that can be used to remove methods.
In this case -=b will remove the last occurrence (in execution order) of the delegate that is equal to b.
Equality between delegates is defined so that equal delegates have to be of the same type and encapsulate the same method – Target and Method. Notice that this is a change from version 1 of C# where equal delegates could be different types.
Also notice that two MulticastDelegates are considered equal if their invocation lists are identical and in the same order.
Delegates don’t provide direct access to their invocation list and you can’t manipulate it directly. However you can retrieve the invocation list as an array of Delegates.
returns a delegate array with two elements – a delegate wrapping Hello and a delegate wrapping Goodbye.
You can even invoke each delegate in the invocation list by enumerating the array:
You might be puzzled as to why we use DynamicInvoke rather than Invoke?
The reason is that neither Delegate nor MulticastDelegate support an Invoke method. When you use the classes directly in this way the invocation of the method cannot be checked at compile time, hence the need for a “DynamicInvoke”.
Some of the simplicity of using a MuticastDelegate comes from the overloading of the += and -= operators but there are other, perhaps surprising ways, that you can work with invocation lists.
For example, you can create a delegate that has the combined invocation list of other delegates using the “+” operator:
In this case DelC encapsulates the methods of DelA and DelB. Similarly the “-“ operator removes one delegate’s invocation list from another.
removes all of the delegates in the invocation list of DelA from DelC.
It is also worth knowing that there are static methods defined on the Delegate class such as Combine and Remove which can be used to manipulate arrays of Delegates as well as pairs of Delegates.
A common misconception is that .NET events are just multicast delegates. This is mostly true but an event has some additional added infrastructure.
Note: We are discussing .NET events not WPF's routed events.
An event is a multicast delegate with the addition of two accessor functions, add and remove, that take over the adding and removal of delegates from the invocation list. Most of the time you can ignore the existence of the accessor functions as the system will provide default implementations for you and, as the += and -= operators are overloaded and call these default accessor functions, you can use an event as if it was a simple delegate.
The basic idea is that a class can provide an event that any clients can subscribe to simply by adding delegates to the invocation list of the event’s multicast delegate. The event can only be “raised” by the class that owns the event and this results in each delegate in the invocation list being called.
Events are mostly generated automatically for us by Visual Studio but to really understand what is going on you can’t do better than implement a custom event manually.
First we need a suitable delegate type:
This we convert into an event:
MyNewEvent is now an event instance which wraps an anonymous instance of the delegate type. We can add a delegate to the event in the usual way:
You can also use anonymous methods and lambda expressions to add delegates to an event:
To raise the event, we simply call it as if it was a delegate:
This is slightly more subtle than it looks because, for a default event with no custom accessor functions, the compiler changes the reference to the event to a reference to the private internal instance of the delegate.
This allows you to call the delegate using the name of the event – but if you do define custom accessor functions this no longer works.
You can use Invoke if you want to but notice that an event restricts the invocation of its delegate to the object in which it is declared.
In other words, an object cannot raise anothe object's event.
The new event object has all of the methods of a delegate and can be used more or less as if it was a delegate.
In particular, you can use BeginInvoke to fire an event asynchronously on a threadpool thread. If you are concerned about using up threadpool threads you could even create a thread manually and run the delegate using it.
By default an event delegate is called synchronously and this means that the code that raises the event is stalled until the event processing is complete.
Are asynchronous events a good idea?
If you want to write a responsive program, then yes they are, but you need to understand the problems that can arise in a multithreaded approach.
Even if you don’t explicitly make use of a multithreaded approach you have to allow for the possibility that your code will be used in this way. Objects running on different threads can and do add event handlers to your event. Events are multi-threaded.
For example, if you want to provide a custom add and remove accessor then you need to code the event something like:
Notice that you need to use a lock to stop other threads from updating the delegate while you are in the middle of doing so. Locking on the current object, i.e. this, isn’t a good idea because other threads might well try to acquire a lock on some other resource using it, so producing unnecessary waiting for locks to be released.
Notice also that you now have to provide a private member variable to store the delegate. You can't use MyNewEvent because using += on it would trigger the accessor functions in an infinite recursion.
As the compiler now has no idea what you have called this internal private variable, you can no longer raise the event using the event’s name. The standard solution is to provide an “On” method something like:
Following this pattern means always calling the On method to raise the event.
This is the pattern used automatically for all built-in events within the .NET Framework. Notice, however, that this isn’t threadsafe because you could check that the invocation list isn’t null and start the call when another thread takes over and changes the invocation list! Again a correct solution is to obtain a lock before processing the invocation list.
In short, events are inherently multithreading and as such are dangerous.
|Last Updated ( Friday, 29 July 2016 )|