views:

409

answers:

2

I have come across what must be a common problem. When I have an event which may be subscribed to by several different classes, an exception thrown by one of these classes will kill the callback chain; as I do not know a priori in what order the callback is carried out, this can result in unpredictable state changes for some classes and not for others.

In the bible (CLR via C#, I am using C# 2.0) there is a short paragraph about using MulticastDelegate.GetInvocationList to get around this, but nothing more. So my question is: what is the best way to deal with this? Do I have to use MulticastDelegate.GetInvocationList every time I have an event?? Or do I need to enclose all methods which may be called as part of the delegate chain in some kind of rollback mechanism? Why are all these options so complicated in comparison to the simple event/delegate model which is so easy to use in C#? And how can I use the simple way without ending up with corrupted state?

Thanks!

+7  A: 

If you simply invoke a delegate, it will call all the target methods in order. You need to use GetInvocationList if you want to execute them individually - for example:

  • to check Cancel after each
  • to capture the return value of each
  • to continue after failure of an individual target

As for the best way to use it: how do you want it to behave? It isn't clear to me... for example, this might suit an extension method quite well:

static void InvokeIgnoreErrors(this EventHandler handler,
        object sender) {
    if(handler != null) {
        foreach(EventHandler subHandler in handler.GetInvocationList()) {
            subHandler(sender, EventArgs.Empty);
        }
    }
}

Then you can just call myHandler.InvokeIgnoreErrors(this); (for example).

Another example might be:

static bool InvokeCheckCancel(this CancelEventHandler handler,
        object sender) {
    if(handler != null) {
        CancelEventArgs args = new CancelEventArgs(false);
        foreach(CancelEventHandler subHandler in handler.GetInvocationList()) {
            subHandler(sender, args);
            if(args.Cancel) return true;
        }
    }
    return false;
}

which stops after the first event requests cancellation.

Marc Gravell
OK, this seems like a nice use for extension methods. Unfortunately I am stuck in C# 2.0 - any ideas how I could do it elegantly here? Thanks!
Joel in Gö
Just use a static method, then. Remove the "this", and just use YourUtilityClass.InvokeIgnoreErrors(myHandler, this);
Marc Gravell
+1  A: 

Rather than changing how you invoke events, I think you should review your event handlers. In my opinion event handler methods should always be written so they are "safe" and never let exceptions propagate. This is particularly important when you're handling events in GUI code where the event is invoked by external code, but a good habit to have at all times.

Helen Toomik
What do you mean by 'propagate'? I clearly may need method #3 in my delegate chain to inform me if it has crashed, as I may then have to roll back any changes carried out in methods #1 and #2 (but not method #4).
Joel in Gö
Then you are misusing the observer pattern, the point of using events is as a loosely coupled notification system. Here not only do the subscribers and publisher need to know implementation details of each other, the subscribers must know implementation details about other subscribers as well.
Stephen Martin
That said there are definitely cases where you would want to use the invocation list rather than just invoking the delegate directly as Marc has described.
Stephen Martin
I meant 'propagate' as in 'spread, reproduce', i.e. exceptions that occur in an event handler should be handled there and not bubble up to the invoker.
Helen Toomik
And ditto to what Stephen Martin says in his comment. Event subscribers should be independent of each other, and the source should function without any listeners. I like to think of it as a radio station.
Helen Toomik
Thanks; the comments about misusing the observer pattern are making me think hard about the architecture here...
Joel in Gö