Cancelable events vs. exception


Interesting question over an internal API design alias:


 


Question: Which is the best way to implement events that developer can cancel.


 


Cancelable events:


In this scenario we would derive the eventArgs from CancelEventArgs so that developers can set the Cancel property if they want to cancel the event.


 


The code would look like:


 


void private FormLoadEventHandler(object sender, FormLoadEventArgs e)


{
  if (user is not authenticated)


  {


    // I don’t want to load the form


    e.Cancel=true;


 }


}


 


 


Throw an exception:


In this case if developers want to cancel the event they will have to throw an exception


 


The code would like:


void private FormLoadEventHandler(object sender, FormLoadEventArgs e)


{
  if(user is not authenticated)


  {


    // I don’t want to load the form


    throw new AbortEventException(“Cannot load”);


 }


}


 


Answer (From Mark Boulter and me):


It sounds like option two is using exceptions as a message passing mechanism which isn’t a good idea pref-wise and does not feel like a natural fit from a user model point of view. You should not throw exceptions to cancel events you should use CancelEventArgs because:


 



  1. Typically the cancellation is an expected result of handling the event and you should not throw exceptions for expected results as mentioned below

 



  1. Throwing exceptions in UI is on the whole a “bad thing” because typically there is no good place for the developer to catch the event – event based UI is non-linear and exceptions rely on linear execution – in many places in Windows Forms we catch exceptions and turn them into error events for precisely this reason

Comments (20)

  1. I’d have to think CancelEventArgs would be more natural in that the args indicate you can cancel it. If you had to use an Exception, there would be a real disconnect in how to cancel the event, which would go against the philosophy of self-documenting code.

  2. "you" could then start adding the cancel property to the DataColumnChangeEventArgs and DataRowChangeEventArgs classes on .NET V 2.0

    my two cents :)

  3. B.Y. says:

    The fact that someone even considered using exception in this scenario, is proof that some people just can’t keep things simple.

    I hope whoever wants to use exception in this scenario get out of designing and programming things.

  4. This one isn’t as straight forward as it sounds. In general I’d prefer the CancelEvent model, however I can see the argument for the event throw model in particular if your code looks like this:

    if (test1Fails()) throw new AbortEventException(“Test1”);

    if (test2Fails()) throw new AbortEventException(“Test2”);

    if (test3Fails()) throw new AbortEventException(“Test3”);

    Then the throw feels more natural as it has an implied return and can also pass back more information. However (as you pointed out) exceptions really should just be for exceptions, not as a subsititute for message passing.

    I think I’d vote for a CancelEvent but it’s marginal.

  5. Since Cancelling the event is a potentially expected outcome, I don’t see how it would be an "Exceptional" case.

    So my vote goes for e.Cancel = true.

  6. honestly, I think that we need something a bit more involved than a boolean cancel (and yes, obviously, the exception route is no good).

    A single event may have many event handlers, and the consumer/developer of the event may not always be aware of which handlers are wired up, and it what order. What if handler A sets e.Cancel to true, but then handler B comes along and sets e.Cancel to false? does handler B even get invoked? what if they’re attached in opposite order? do the handlers have side effects?

    I think we need something similar to the way that transactions work… perhaps cancelableEventArgs can have an overridable method along the lines of RequestEventCancelation(object source, object state), in which the developer of the cancelable event could try to determine if the processing should actually stop *right now* or not.

  7. Neil Groves says:

    I agree with Chris Hollander. I think there is something wrong here.

    Certainly the exception handling case should be completely ruled out because of your 2nd point. Throwing in the UI thread is just evil.

    Cancelling does not really lend it self to a linear event model? The behaviour would depend on the sequence in which the handlers were registered. It also means that the event handlers have side-effects on other event handlers.

    It seems to me that cancelling should normally not be implemented in a list of event handlers. If cancellation is required a hierarchy of event handlers makes much more sense IMHO. A root can "decide" whether or not to pass the event on to it’s children. This is what I have done for many of my designs, and it seems to work better than any alternatives I have thought of.

    HTH.

  8. Brad Abrams says:

    Good feedback, thanks!

    enrico – In this case consider setting the Error property on the Column….

  9. [ Via Brad Abrams ]Cancelable events vs. exception Interesting question over an internal API design alias: Question: Which is the best way to implement events that developer can cancel. Cancelable events:In this scenario we would derive the eventArgs from CancelEventArgs so that…

  10. DataError says:

    I support the cancelEvent model as the others.

    But instead of just a boolean field, can you make it to be an object of some type? Like all exception is derived from Exception. Maybe cancelEvent can be an object that derived from EventCancel class.

    This can give us the flexibility to provide more info on why the event is cancel and wheater it should continue with the event chain in the stack if the event itself also lead to the firing of other events or there are other event that is fire together in sequence with this event (Eg, BeforeUpdate, Update, AfterUpdate).

  11. In the case of multiple EventHandlers, surely you would have to execute each handler in turn from the root and check wether the Event has been canceled after each call.

    if(MyEventHandler!=null){

    MyEventArgs args=new MyEventArgs(this,"Some Data");

    MyEventHandler mah=null;

    foreach(Delegate del in MyEventHandler.GetInvocationList()){

    try{

    mah=(myEventHandler)del;

    mah(args);

    if(args.Cancel){

    break; //this delegate cancelled, so don’t call others

    }

    }

    catch(Exception ex){

    MyEventHandler-=mah; //delegate not available, so ditch it

    }

    }

    }

  12. thank for the suggestion Brad, unfortunately the Error property on the Column does not avoid having the wrong value pushed into the DataRow.

    So you have an entity in an invalid state with a marker pointing you to what’s wrong. This might be what you want in some scenario, but not in lot of others.

    The real bad thing is that if the datatable is bind to a datagrid, the datagrid will catch the exception and show up a messagebox you have no control on (as far as I know).

  13. If message passing is an issue, the EventArg object should contain a ‘CancelMessage’ property or something, so that when the event is bubbled up, the «next» object can read the ‘CancelMessage’ if the event was cancelled. Throwing exceptions is not a good solution here.

  14. Brad Abrams says:
  15. As I re-read the comments here one thing stands out to me. As some of you have said earlier it is important to not forget first principles for exception handling… After all even with the event hander model the event handler could throw an exception. Exceptions are the error handing mechanism in the CLR… we should not go create a new model for handling errors throw events (by figuring out how to pass complex state, etc), we already have exceptions for that.

    So if the cancellation contract is clear and well understood… you know exactly what it means to set Cancel=true, then this is a fine model. But if you are trying to communicate some kind of error condition with state information, then an exception would be better.

  16. I don’t like the idea of throwing an exception. I would like to see a solution where we don’t need to set the Cancel property to true, maybe this could be done with an Cancel method instead added to the event argument, any comment on this idea?

    e.Cancel();

  17. Danny Thorpe says:

    This discussion actually brings up the question of why doesn’t WinForms provide a way for an application to handle exceptions across the entire application?