|Deep C# - Take Exception to Everything|
|Written by Mike James|
|Thursday, 12 March 2020|
Page 4 of 5
The state problem
Mention of the stack trace brings us to the central problem of exception handling.
Exceptions in C# and Windows in general are "structured" in the sense that they are nested according to the call stack. If a method raises an exception and doesn't have an exception handler then the system looks back through the call stack to find the first method up the chain that does. This is perfectly reasonable behaviour and in fact without out it exception handling would be a waste of time.
For example, suppose you write a try block:
and a set of carefully constructed catch blocks that handle every possible exception. Now suppose that "do things" makes method calls. If any exception that occurred within a method that didn't handle it wasn't passed up the call stack then our efforts at comprehensive exception handling would be nonsense - the program could still crash with a runtime error within the try block.
However exception handling has some deep problems if you are planning to allow the program to continue after an exception.
In practice most programmers simply use exception handling as a way of allowing the application to make a soft landing rather than crash. It saves face rather than being useful. Typically they provide a more informative, or more friendly, error message that tells the user who to contact and what information to provide. Of course this is all Windows dressing because if the exception really is an exception and not an error state that the programmers were too lazy to detect then there will be nothing that can be done to correct it - other than improve or fix the hardware.
Suppose, however, that you are trying to use exceptions to allow an application to continue. This is a much more difficult problem.
The first idea you need to accept is exception safe code.
If the code in the try block raises an exception then you have to ask what side effects are there when you reach the catch block. Side effects are the change in state that the try block has implemented up to the point that it raised the exception. For example, if the try block opened a file then it is still open. Side effects include deeper problems such as memory leaks and orphaned resources in general.
Notice that the try block has its own scope so if you stick to the rule that the code in a try block will not access anything not created in the block it is almost (but not) guaranteed to be exception safe.
Notice also that the very act of propagating the exception up the call stack of necessity unwinds the state back to the method that handles the exception.
That is, if you have a catch block that is triggered by an exception thrown by a method called within the try block then all trace of the internal state of that method has gone. This doesn't make cleaning up after the method an attractive proposition. Basically if your aim it to continue the application you need to handle exceptions at the lowest level and avoid unwinding the call stack - which sort of makes the whole idea of structured exception handling a bit of a nonesense.
A two-step process
The best way to think of implementing exception safe code in practice is probably to think of it as a two step - commit and rollback operation.
The try block attempts the commit and the catch implements the rollback.
In this scenario all of the objects have to be created before the try-catch block - otherwise the try and catch blocks couldn't operate on the same variables. This still leaves you open to some exceptions - for example running out of memory while creating objects.
There seems to be no way around the scope problems that try catch introduces.
Consider the response to a timeout exception for a moment. Perhaps what you want to do is give the user a chance to correct some data - a website address, say- and then try again.
This leads to the idea of Resume or Resume Next which are attractive propositions - until you realise the scope for misunderstanding its use could make, and has made, a mess of many an application.
Basic, but not VB .NET made Resume available and in unskilled hands it didn't work well and was the reason why VB .NET has structured exception handling.
However the basic response
"something strange has happened where can we resume application execution"
is still reasonable..
Using try-catch you can create the same effect as Resume Next if you are prepared to do a lot of work. For example, suppose the correct result of a/0 is deemed to be 0 - don’t take this example too seriously in its detail - then you can "resume next" using:
Once again note that all the variables have to be defined before the try block. This just makes use of the basic behaviour of the try-catch - that execution continues after the last catch or the finally clause.
Notice that you can't protect multiple arithmetic statements or method calls that do the same arithmetic unless you are prepared to add try-catch around each one. In other words, there is no global way of resuming after an exception.
Many are of the opinion that such a thing would be a bigger exception waiting to happen but some just think that we haven't figured out how to implement such a facility in the right way.
If you want to resume, i.e. retry, the statement that caused the problem then this is really difficult. Using the same manual method you could simple write the statement out again:
but now the second attempt isn't protected.
You could use:
although now what to do in the inner try-catch is becoming a little contrived. It makes more sense to consider retrying in this way following a timeout or a file read error but the basic idea is sound and leads to constructions like:
You can think of this as try, put it right, and try again. In practice you would need to add something that terminated the loop when the retry had been attempted sufficiently often. There is also the problem of making sure that the try (or the catch) does not leave side effects that are accumulated with each repetition.
You can see that the try-try again pattern has the potential for making things worse as well as better.
|Last Updated ( Thursday, 12 March 2020 )|