Command Connection Points and Exception Handling Reloaded

In Debugging of and error handling for synchronous Commands I explained why you can't handle exceptions thrown during the execution of commands and how to get around this limitation. Yesterday I was reading through some posts on Eric White's blog including some about the new language features in C# 3.0. One post about extension methods reminded me of the problem with our command connection points. I should mention that we fixed a bug for the July CTP which broke some new C# language features including extension methods (which means that the following will not work with CTP 1).

So, what is an extension method? In a nutshell: An extension method is a static method that can be called as if it were an instance method of an existing type. That said, we still would like to execute commands using the exception handling we're used to and while the code in this post is still a workaround it would be great to have one which does not require changes to the command implementation and minimizes the impact on the command caller. But we can't change the implementation of the actual IComponentCommand<object> type as it is part of Acropolis' Common Framework. However, with this new language feature we can implement what we need and make it look like it were a method on the original IComponentCommand<object> type. Let's start by adding a reference to the assembly System.Core to our project because otherwise it will not compile after we declare our extension method. After that we can go ahead and refactor the code of the original workaround:

public static class ComponentCommandExtensions

{

    public static void ExecuteAndRethrowException(this IComponentCommand<object> arg, object parameter)

    {

        using (EventWaitHandle waitHandle = new EventWaitHandle(false, EventResetMode.AutoReset))

        {

            Operation op = (Operation)arg.BeginExecute(

                parameter,

                new OperationStatusCallback(OperationStatusCallbackHandler),

                waitHandle);

            waitHandle.WaitOne();

            if (op.ExecutionException != null)

                throw op.ExecutionException;

        }

    }

    private static void OperationStatusCallbackHandler(Operation operation, OperationCallbackReason reason)

    {

        if (reason == OperationCallbackReason.OperationCompleted)

            ((EventWaitHandle)operation.UserState).Set();

    }

}

Now we have a static method which creates the EventWaitHandle object, waits for it to change its state to signaled and then throws an exception if necessary. We are still "abusing" the parameter userState of the method IComponentCommand<object>.BeginExecute() for passing around our wait handle but this time we also register an operation status callback method which sets the wait handle when the operation is completed. Because of this we don't have to have any special code in the command implementation anymore so the little proof of concept implementation I used last time (whose only purpose was and still is to throw an exception) boils down to:

private void FailingCommand_CommandExecuted(object sender, ComponentCommandExecutionEventArgs<object> e)

{

    throw new NotImplementedException();

}

And because we declared ExecuteAndRethrowException() as an extension method we can now call it as if it were defined on the type IComponentCommand<object> itself. The only drawback left is that we lose the frames on the stack before ComponentCommandExtensions.ExecuteAndRethrowException(). This will most likely not affect any real world exception handling at runtime but may make debugging a bit more complicated (but it's nothing that setting a break point and examining Operation.ExecutionException before it is re-thrown can't solve).

private void TestButton_Click(object sender, RoutedEventArgs e)

{

    try

    {

        Part.FailingCommand.ExecuteAndRethrowException(null);

    }

    catch (Exception exception)

    {

        MessageBox.Show(

            "Caught '" +

            exception.GetType().ToString() +

            "' exception in TestButton_Click(object, RoutedEventArgs)");

    }

}


This posting is provided "AS IS" with no warranties, and confers no rights.