TRY, CATCH and FINALLY are used to declare an exception handling block.
TRY
guardedStatements
[CATCH [[variableName] AS exceptionType] [WHEN whenexpression]
exceptionHandlingStatements
]
[FINALLY
cleanupStatements
]
END TRY
variableName | The name of a variable that will receive the exception. The variable name is optional. If you only specify the type then the exception will still be caught but not stored in a local variable. |
exceptionType | The exception type that will be caught by the CATCH block. |
whenexpression | A logical expression that determines if the particular CATCH block should be active |
exceptionHandlingStatements | Zero or more statements that handle the exception condition. |
cleanupStatements | Zero or more statements that perform any necessary cleanup before the TRY block is exited.. |
A TRY-CATCH-FINALLY block is used to trap and handle exceptions that may be thrown within a block of code. Exceptions may be generated by the CLR, the Vulcan.NET runtime library, third-party libraries or by application code using the THROW statement.
The statements within the TRY block are sometimes referred to as "guarded" statements. These are the statements that potentially can cause exceptions that you want to handle.
A exception handling block may contain any number of CATCH blocks (including zero). Each CATCH block that declares a variable name and a type will receive exceptions of that type. CATCH blocks that receive exceptions implicitly declare a local variable that will contain the caught exception. This implicitly declared local variable is only valid within the scope of the CATCH block. The name given to the variable must not be the same as any explicitly declared LOCAL or parameter, or a compile-time error will occur. However, it is legal to use the same variable name in multiple CATCH blocks. Since each CATCH block's variable is only visible within its enclosing block, there is no conflict.
A CATCH block may also be declared without a variable name but WITH an exception type. In that case the exception will still be caught but not stored in a local variable.
A CATCH block may also be declared without any variable name and exception type. This type of CATCH block will catch any exception, and is equivalent to declaring a CATCH block with an exception type of System.Exception.
If multiple CATCH blocks are declared, the order in which they appear is important. The CLR will examine the CATCH clauses in order, and invoke the first one that matches the exception being thrown. This includes not only the specific exception class that was specified, but any derived classes. For this reason, you should catch the more specific exception types before less specific ones.
The exception type declared in a CATCH block must always be System.Exception, or a class derived from it.
If no suitable CATCH block was declared for the exception that has been thrown, control will be passed to the next highest exception handling block. If there is no higher exception handling block, or none that can handle the exception, the application will terminate.
Exceptions may be explicitly passed on to the next highest exception handling block by using the THROW keyword.
If a FINALLY block is declared, any statements within it are executed regardless of how the TRY block exits. This provides a mechanism to perform any cleanup such as releasing resources. The code within a finally block will be executed even if there is no suitable CATCH block to handle the exception.
TRY-CATCH-FINALLY blocks are similar to, but much more flexible than BEGIN SEQUENCE-RECOVER blocks. However, BEGIN SEQUENCE and RECOVER are still supported for backwards compatibility.
Note that exceptions thrown with BREAK will not be caught with a TRY-CATCH-FINALLY block because the data thrown by BREAK is encapsulated in a USUAL, which does not inherit from System.Exception.
However, exceptions thrown with THROW will be caught by the next highest BEGIN SEQUENCE block (if any) and the exception will be encapsulated in a USUAL if a RECOVER USING variable has been declared.
Also note that the CanBreak() runtime function does not detect whether execution is currently within a TRY block. CanBreak() is provided only for compatibility with existing Visual Objects code and BEGIN SEQUENCE blocks, and should not be relied upon to determine whether execution is within an exception handling block. There is no way to determine whether execution is within an exception handling block because this functionality is not present in the CLR, and execution may currently be within code that is compiled in an language other than Vulcan.NET.
The following example tests for division by zero and catches the exception that will be thrown by the CLR if the divisor is zero. Any other exceptions would propagate to the next highest exception handling block (if any). Without the exception handling block, the application would terminate with an unhandled DivideByZeroException.
FUNCTION DivisionTest( x AS INT, y AS INT ) AS INT
TRY
RETURN x / y
CATCH e AS System.DivideByZeroException
? "Divide by zero!", e
RETURN 0
END TRY
The following example demonstrates multiple CATCH blocks and a FINALLY block:
USING System.IO
FUNCTION ReadFile( filename AS STRING ) AS STRING
LOCAL s AS STRING
TRY
s := File.ReadAllText( filename )
CATCH e AS DirectoryNotFoundException
? "Directory not found", e
CATCH e AS IOException
? "IO exception occurred", e
CATCH e AS UnauthorizedAccessException
? "Access denied", e
CATCH
? "Some other exception"
FINALLY
? "All done!"
END TRY
RETURN s