The .NET framework supports exceptions. When a piece of code generates an error condition, the system raises an exception. By default, this will cause the program to terminate with a diagnostic message. However, if the code lies within a TRY block then it is possible to respond to the error and continue execution. The same is true if the error occurs within a subprogram that has been called from within a TRY block.
Raising an exception is an expensive process, so you should design your code so that exceptions are only raised under genuine error conditions. The practise of using exceptions as a device to perform non-local jumps should be avoided. In contrast, entering or leaving a TRY block has only a small overhead unless and until an exception is raised.
Here is a simple example of code containing a TRY block:
STOP 'Here we go'
PRINT*,'This should never print'
PRINT*,'In CATCH block'
PRINT*,'In FINALLY block'
A TRY block is terminated by ENDTRY. The TRY statements in the block are followed by a CATCH block. A CATCH block is terminated by the beginning of another CATCH block or by FINALLY or by ENDTRY. A single FINALLY block (if present) is written at the end and is terminated by ENDTRY. Statements in a CATCH block are only executed when the corresponding exception is raised by one of the TRY statements at which point control is transferred to the CATCH block and any remaining statements in the TRY block are skipped.
The FINALLY block is always executed so terminating TRY statements that must not be skipped (when an exception is raised) should be written in the FINALLY block.
A STOP statement that has a message string raises an exception. Consequently the program above produces the following output:
In CATCH block
In FINALLY block
If the STOP statement is removed from the program an exception is not raised and the output is:
This should never print
In FINALLY block
Although this is an artificial example, it illustrates the fact that, when an exception is raised, the remaining TRY statements (before CATCH or FINALLY) are abandoned and control is passed to the CATCH block. In either case (whether or not an exception is raised) statements in a FINALLY block are then executed.
CATCH takes an argument that specifies the exception that is to be handled by the block. The system matches the kind of exception that has been raised with the argument of the CATCH block. The exception can match exactly (as in this example). Alternativley you can use a base class (e.g. System.Exception) when declaring the variable (myexception) in order to catch all exceptions that are members of the base class. See the .NET documentation for information about the relevant class members.
When an exception is raised and not caught the program terminates and with diagnostic information that includes the name of the exception. This name can be copied and written into the code so that the exception will be caught in future. See also Arithmetic overflow/underflow .
You can raise your own exceptions using a THROW statement. In C#, a throw takes an argument that is an expression (typically a variable) that evaluates to a .NET exception class. FTN95 recognises a similar syntax but for simplicity it also allows you to use a CHARACTER string variable as an argument to specify the name of the class directly.
Thus in FTN95 a THROW statement can take the form:
This string variable can also be used as the argument for the corresponding CATCH statement. (When you use THROW with a character string argument, FTN95 raises an exception using a class called Salford.Fortran.UserException. This class has a member that is a string variable whose value is the string that has been used as the argument for THROW.)
When using a CHARACTER string variable to THROW an exception it is still possible handle multiple exceptions. To do this you simply re-throw any exceptions that are not handled by the current CATCH. For example:
!Handle two particular exceptions and throw the rest to a possible outer try block
IF(EXC /= 'MyException_1' .AND. EXC /= 'MyException_5') THROW EXC
These CHARACTER variables are standard Fortran strings so you should ensure that they are long enough to avoid truncation.