Problem background
An event can have multiple subscribers (i.e. multiple handlers may be called when an event is raised). Since any one of the handlers could throw an error, and that would prevent the rest of them from being called, I want to ignore any errors thrown from each individual handler. In other words, I do not want an error in one handler to disrupt the execution of other handlers in the invocation list, since neither those other handlers nor the event publisher has any control over what any particular event handler's code does.
This can be accomplished easily with code like this:
public event EventHandler MyEvent;
public void RaiseEventSafely( object sender, EventArgs e )
{
foreach(EventHandlerType handler in MyEvent.GetInvocationList())
try {handler( sender, e );}catch{}
}
A generic, thread-safe, error-free solution
Of course, I don't want to write all this generic code over and over every time I call an event, so I wanted to encapsulate it in a generic class. Furthermore, I'd actually need additional code to ensure thread-safety so that MyEvent's invocation list does not change while the list of methods is being executed.
I decided to implement this as a generic class where the generic type is constrained by the "where" clause to be a Delegate. I really wanted the constraint to be "delegate" or "event", but those are not valid, so using Delegate as a base class constraint is the best I can do. I then create a lock object and lock it in a public event's add and remove methods, which alter a private delegate variable called "event_handlers".
public class SafeEventHandler<EventType> where EventType:Delegate
{
private object collection_lock = new object();
private EventType event_handlers;
public SafeEventHandler(){}
public event EventType Handlers
{
add {lock(collection_lock){event_handlers += value;}}
remove {lock(collection_lock){event_handlers -= value;}}
}
public void RaiseEventSafely( EventType event_delegate, object[] args )
{
lock (collection_lock)
foreach (Delegate handler in event_delegate.GetInvocationList())
try {handler.DynamicInvoke( args );}catch{}
}
}
Compiler issue with += operator, but two easy workarounds
One problem ran into is that the line "event_handlers += value;" results in the compiler error "Operator '+=' cannot be applied to types 'EventType' and 'EventType'". Even though EventType is constrained to be a Delegate type, it will not allow the += operator on it.
As a workaround, I just added the event keyword to "event_handlers", so the definition looks like this "private event EventType event_handlers;
", and that compiles fine. But I also figured that since the "event" keyword can generate code to handle this, that I should be able to as well, so I eventually changed it to this to avoid the compiler's inability to recognize that '+=' SHOULD apply to a generic type constrained to be a Delegate. The private variable "event_handlers" is now typed as Delegate instead of the generic EventType, and the add/remove methods follow this pattern event_handlers = MulticastDelegate.Combine( event_handlers, value );
The final code looks like this:
public class SafeEventHandler<EventType> where EventType:Delegate
{
private object collection_lock = new object();
private Delegate event_handlers;
public SafeEventHandler(){}
public event EventType Handlers
{
add {lock(collection_lock){event_handlers = Delegate.Combine( event_handlers, value );}}
remove {lock(collection_lock){event_handlers = Delegate.Remove( event_handlers, value );}}
}
public void RaiseEventSafely( EventType event_delegate, object[] args )
{
lock (collection_lock)
foreach (Delegate handler in event_delegate.GetInvocationList())
try {handler.DynamicInvoke( args );}catch{}
}
}
The Question
My question is... does this appear to do the job well? Is there a better way or is this basically the way it must be done? I think I've exhausted all the options. Using a lock in the add/remove methods of a public event (backed by a private delegate) and also using the same lock while executing the invocation list is the only way I can see to make the invocation list thread-safe, while also ensuring errors thrown by handlers don't interfere with the invocation of other handlers.