Page 3 of 3
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 another’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:
private MyNewEventType _MyNewEvent;
public event MyNewEventType MyNewEvent
_MyNewEvent += value;
_MyNewEvent -= value;
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:
private void OnMyNewEvent(string param)
if (_MyNewEvent != null)
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.
Generic and standard events
Of course most of us simply make use of predefined events but there has been a change in the way that this works. Originally we needed a delegate type for each even slightly different even or we just passed object types to allow the event handler to work with a range of types. A better solution is to use generics and this is the approach now taken by the framework classes.
For example, the original standard event handler was no generic:
public delegate void EventHandler(
object sender, EventArgs e);
Using object as the first parameter allowed any class to raise the event and still notify the users of the event handlers what had raised the event. The new generic version is:
public delegate void
(object sender, TEventArgs e)
where TEventArgs : EventArgs;
which still leaves the sender untyped. A better version is:
public delegate void
(S sender,A args);
In this case the event would be set up using something like:
public event GenericEventHandler
Generics significantly simplify the implementation of events and by reducing the need to pass an object type increase overall type safety.