Exception Handling
This is a Language topic about Oxygene
Language Topics Introduction | Structured Overview | Grammar | Keywords | Functions
Exception Handling Overview
Exceptions are a standard mechanism in most current structured programming languages that are used to signal error conditions and abort the current flow of execution when errors occur.
Before the introduction of exception handling, errors were usually indicated by returning failure or success codes from method calls, so common code was riddled with checks for these results and if/then clauses that tied further execution to success. What's more, failure to check for results lead to these error conditions simply being ignored and code running on - oblivious to the fact that a prior method call had failed.
With exceptions, the default behavior when an exception is raised (or thrown, in C++/C# terminology) is that the current flow of execution is interrupted, and the Exception will be propagated up the call stack until it is caught by one or more exception handlers.
Writing Exception Handlers
There are two ways to handle an exception:
- a try/except block can be used to catch the exception, do optional error handling and have execution continue as normal from that point on.
- a try/finally can be used to perform clean-up in case of an exception, but have the excpetion continue to bubble up uncaught, afterwards.
Catching Exceptions
In normal day-to-day development, there are very few occasions where user code will actually want to catch exceptions, and application developers should take care to only catch (and handle) very specific exceptions that they except to occur, and let any other "unexpected" exceptions bubble up to the top.
Exceptions may be caught using a try/except code block, which in its simplest form looks like this (again, it is not good practice to blindly catch all exceptions, it is only shown here for illustration purposes):
try // ...code goes here... except Console.WriteLine('There was an exception'); end;
The code inside the except block will only be executed in an exception that actually occurred. If the code between try and except finished without problems, execution will skip the except block and resume after the construct.
In either case, execution will resume after the end as if the exception has never happened.
Re-raising Exceptions
The developer can also choose to re-raise the exception, using the raise keyword, in order to provide exception handling but not catch the Exception. This way, the exception will continue to bubble up the call stack.
try // ...code goes here... except Console.WriteLine('There was an exception'); raise; // let the exception bubble up further end;
Performing Cleanup
Rather then catching exceptions, developers can also use the try/finally block to provide cleanup code that will always be executed - whether an exception occurs or not. This way, the developer can make sure the critical resources are freed or other tasks are performed, and the current method (or section of code) is left in a clean state, even when exceptions occur.
var o := new SomeObject try // ...code goes here... finally o.Dispose(); end;
Here, the code inside the finally block will always be executed, even if an exception occurred within the try block. The exception will not be caught, but continue to bubble up the call stack.
The second block always executes, even if an exception has been raised.
Combining except and finally blocks
Oxygene allows you to combine these exception handler types in a single try block, to perform both cleanup and catch the exception:
try // ...code goes here... except // ...your exception code... finally // ...your cleanup code... end;
Exception Selectors
Exception Selectors can be used to limit the scope of a try/except block to one or more exception types. When specified, the exception handler will only catch exceptions of the specified types, and let any other exceptions bubble up.
A second advantage of exception selectors is, that by providing an (optional) variable name for the exception, you can also gain access to the exception objects, to inspect their properties (for example to emit the Message property to a log).
try // ...code goes here... except on E: SpecialException do Console.WriteLine('A special exception occurred. Details: '+E.SpecialDetails); end;
Multiple exception classes can be listed in the same exception selector, and more than one selector can be provided within the same except block. If more than one selector is present, the run-time will look at them in the order of definition, and execute the first selector that matches the current exception.
If no selector is matched, the exception handler will be skipped and the exception will bubble up the stack.
Note: An else clause in except blocks, as known from Delphi for Win32, is currently not supported by Oxygene. To make sure any exception is caught in a specific except block, put a selector for type Exception as the very last line into the except block. Since all exceptions (should) descend from type Exception, this will catch all exceptions which are not handled by any of the previous selectors.
try // ...code goes here... except on E: SpecialException, CoolException do Console.WriteLine('Either a special or a cool exception occurred. Message: '+E.Message); on ArgumentException do // optional variable name is omitted Console.WriteLine('An AgumentException occurred.'); on Exception do //This catches all other exceptions (name is optional too). Console.WriteLine('Some other exception occurred.'); end;
Each selector can be followed by one statement – including, of course, a begin/end block containing more statements.
When more than one exception type is specified in a single selector, the (optional) variable will be defined as the earliest shared ancestor of all types. In the above example, if both SpecialException and CoolException descended from SharedException, then E would be of that type (i.e. it would not allow access to members specific to SpecialException or CoolException, without casting).
Exception Filters
Exception filters allow you to use more complex logic than just a simple type check provided by the exception selectors described above. By using the where keyword, you can append a boolean statement to the selector, to further qualify the conditions that need to be met for this handler to catch the exception:
try // ...code goes here... except on E: SocketException where E.ErrorCode = 10054 do begin // connection was closed gracefully end; end;
In the above example, the handler will only execute if the exception is of type SocketException and if its ErrorCode property equals 10054. In any other case, the exception handler will be skipped and the exception will bubble further up.
Note that this is not the same as providing an if clause within a plain Exception selector. In that case, any SocketException would be caught by the handler and not only would the developer need to manually determine whether to call raise; to re-raise the exception if the error code did not match, there would also be run-time overhead incurred to catch and re-raise.
More details on exception filters can be found in the Visual Basic .NET documentation (yes, VB.NEt was the first language to expose this exciting .NET feature, and in fact it is still not available in C#, as of now.).
Raising Exceptions
New exceptions can be raised from code manually at any time, using a variation of the raise keyword you have already seen below.
Note: In theory, any object can be raised as an exception, but it is common rule (and actually part of the CLS Standard) to only raise exceptions with types that descend from System.Exception.
Also, while you can use System.Exception itself, it is usually recommended to find (or define) a more suitable sub-class to describe the exact nature of the exception in more detail. This can be a sub-class provided by the FCL, where suitable, or a class you define yourself. Defining a class yourself also gives you the opportunity to provide additional properties on the exception class, to describe the error condition in more detail.
Using a proper class hierarchy for raised exceptions makes it easier for the consuming code to use exception selectors to catch particular errors.
if MyObject = nil then raise new ArgumentNullException('MyObject not initialized');