Fundamentals of Exception Handling

Introduction

An error is something that is not part of the regular proceeding of a program. It manifests in various ways:

To help you address such issues, the F# language provides a series of mechanisms referred to as exception handling.

Trying an Exception

In all of the programs we have used so far, we assumed that everything would work alright. In reality, in some cases, something would go wrong. In a section of code where something could go wrong, you are asked to try evaluating that section. You start with the try keyword. The part is referred to as the try section and it contains one or more statements:

try
    statement(s)

This is where you process the regular statement.

Handling an Exception

If everything in the try section is alright, the program proceeds regularly. If something goes wrong, you can address the issue in the subsequent section that uses the with keyword. The formula to follow is:

try
    statement(s)
with
    handler

If the statement(s) in the try section is(are) alright, there is no error and the program proceeds as intended. If something goes wrong, you use the with section to indicate what to do. The with section uses a matching mechanism that has one or more cases. A case can use the following formula:

| case -> handling

There are different ways you can use a case. One of the formulas to use is:

| identifier when condition

An identifier can be any letter or appropriate word. This is followed by the when keyword and a Boolean operation that evalues to true or false. This is followed by -> and what to do. If you don't have any specific way to take care of it, you can just add empty parentheses. Here is an example:

let mutable radius = 0.00
let mutable diameter = 0.00

try
    radius <- 36.84
with
    | wrongValue when (radius < 0.00) -> ()

diameter <- radius * 2.00

printfn "Circle Characteristics"
printfn "----------------------"
printfn "Radius:   %0.02f" radius
printfn "Diameter: %0.02f" diameter
printfn "======================"

This would produce:

Circle Characteristics
----------------------
Radius:   36.84
Diameter: 73.68
======================

Press any key to close this window . . .

As an alternative, you can display a message using the printf() or the printfn() function. Here is an example:

let mutable radius = 0.00
let mutable diameter = 0.00

try
    radius <- 36.84
with
    | wrongValue when (radius < 0.00) -> printfn "Something went wrong when setting the value of the radius."

diameter <- radius * 2.00

printfn "Circle Characteristics"
printfn "----------------------"
printfn "Radius:   %0.02f" radius
printfn "Diameter: %0.02f\n" diameter

Raising an Exception

Introduction

Somewhere in your program, you can detect where something would go wrong and create an exception. This is referred to as raising an exception. This is done using a function named raise. This function takes one argument that can be a string. To start, somewhere before addressing the exception, create a line of code using a keyword named exception. The formula to follow is:

exception function-name of parameter(s)

Start with the exception keyword followed by a name that will be used as a function. The name is followed by the of keyword followed by a type. We will come back to the issue.

In the section of code where a statement is evaluated, create a conditional statement that finds out whether there is an error. In this case, call the raise() function to which you pass the function-name with, if necessary, its argument(s).

In the with section, call the function-name as a case.

A Function With No Argument

If the function doesn't take an argument, use unit as its parameter. When raising the exception, pass the function with empty parentheses to the raise() function. Also, in the with section, call the function-name with empty parentheses. Here is an example:

let mutable radius = -28.15
let mutable diameter = 0.00

exception Msg of unit

try
    if radius < 0.00 then
        raise (Msg())

    diameter <- radius * 2.00

    printfn "Circle Characteristics"
    printfn "----------------------"
    printfn "Radius:   %0.02f" radius
    printfn "Diameter: %0.02f\n" diameter
with
    | Msg() -> printfn "Something went wrong."

Passing an Argument

If the function is taking one argument, specify its type as the parameter. When raising the exception, pass the function with the desired argument in the parentheses of the function. Do the same in the with section. Here is an example:

let mutable radius = -28.15
let mutable diameter = 0.00

exception Msg of string

try
    if radius < 0.00 then
        raise (Msg("Negative values are not allowed."))

    diameter <- radius * 2.00

    printfn "Circle Characteristics"
    printfn "----------------------"
    printfn "Radius:   %0.02f" radius
    printfn "Diameter: %0.02f\n" diameter
with
    | Msg(msg) -> printfn "%s" msg

Passing Many Arguments

If the function is taking more than one argument, enter their types separated by *. When raising the exception, pass the function to the raise() function. Make sure you pass the appropriate arguments to the function-name. Here is an example:

let mutable radius = -28.15
let mutable diameter = 0.00

exception Msg of string * double

try
    if radius < 0.00 then
        raise (Msg("The values you provided is not valid:", radius))

    diameter <- radius * 2.00

    printfn "Circle Characteristics"
    printfn "----------------------"
    printfn "Radius:   %0.02f" radius
    printfn "Diameter: %0.02f\n" diameter
with
    | Msg(str, value) -> printfn "%s %f" str value

Raising Many Exceptions

In the try section, you can raise as many exceptions as you want. One option is to use various conditional statements to raise each exception. In this case, in the with section, create a matching pattern for each case. To start, you can create the appropriate exception function for each case. Here is one example:

let mutable radius = 0.00
let mutable strRadius = ""
let mutable diameter = 0.00

exception GetErrorType of unit
exception ShowMessage of string
exception IdentifyValue of string * double

try
    if strRadius = "" then
        raise (GetErrorType())
    elif strRadius = "0.00" then
        raise (ShowMessage("Null values are not allowed."))

    radius <- (double strRadius);

    if radius < 0.00 then
        raise (IdentifyValue("The values you provided is not valid:", radius))
    
    diameter <- radius * 2.00

    printfn "Circle Characteristics"
    printfn "----------------------"
    printfn "Radius:   %0.02f" radius
    printfn "Diameter: %0.02f\n" diameter
with
    | GetErrorType() -> printfn "The value you provided could not be evaluated."
    | ShowMessage(msg) -> printfn "%s" msg
    | IdentifyValue(str, value) -> printfn "%s %f" str value
    | _ -> ()

This would produce:

The value you provided could not be evaluated.
Press any key to continue . . .

Here is another version of the program:

let mutable radius = 0.00
let mutable strRadius = "0.00"
let mutable diameter = 0.00

exception GetErrorType of unit
exception ShowMessage of string
exception IdentifyValue of string * double

try
    if strRadius = "" then
        raise (GetErrorType())
    elif strRadius = "0.00" then
        raise (ShowMessage("Null values are not allowed."))

    radius <- (double strRadius);

    if radius < 0.00 then
        raise (IdentifyValue("The values you provided is not valid:", radius))
    
    diameter <- radius * 2.00

    printfn "Circle Characteristics"
    printfn "----------------------"
    printfn "Radius:   %0.02f" radius
    printfn "Diameter: %0.02f\n" diameter
with
    | GetErrorType() -> printfn "The value you provided could not be evaluated."
    | ShowMessage(msg) -> printfn "%s" msg
    | IdentifyValue(str, value) -> printfn "%s %f" str value
    | _ -> ()

This would produce:

Null values are not allowed.
Press any key to continue . . .

Here is another version of the program:

let mutable radius = 0.00
let mutable strRadius = "-35.85"
let mutable diameter = 0.00

exception GetErrorType of unit
exception ShowMessage of string
exception IdentifyValue of string * double

try
    if strRadius = "" then
        raise (GetErrorType())
    elif strRadius = "0.00" then
        raise (ShowMessage("Null values are not allowed."))

    radius <- (double strRadius);

    if radius < 0.00 then
        raise (IdentifyValue("The values you provided is not valid:", radius))
    
    diameter <- radius * 2.00

    printfn "Circle Characteristics"
    printfn "----------------------"
    printfn "Radius:   %0.02f" radius
    printfn "Diameter: %0.02f\n" diameter
with
    | GetErrorType() -> printfn "The value you provided could not be evaluated."
    | ShowMessage(msg) -> printfn "%s" msg
    | IdentifyValue(str, value) -> printfn "%s %f" str value
    | _ -> ()

This would produce:

The values you provided is not valid: -35.850000
Press any key to continue . . .

Here is one more version of the program:

let mutable radius = 0.00
let mutable strRadius = "35.85"
let mutable diameter = 0.00

exception GetErrorType of unit
exception ShowMessage of string
exception IdentifyValue of string * double

try
    if strRadius = "" then
        raise (GetErrorType())
    elif strRadius = "0.00" then
        raise (ShowMessage("Null values are not allowed."))

    radius <- (double strRadius);

    if radius < 0.00 then
        raise (IdentifyValue("The values you provided is not valid:", radius))
    
    diameter <- radius * 2.00

    printfn "Circle Characteristics"
    printfn "----------------------"
    printfn "Radius:   %0.02f" radius
    printfn "Diameter: %0.02f\n" diameter
with
    | GetErrorType() -> printfn "The value you provided could not be evaluated."
    | ShowMessage(msg) -> printfn "%s" msg
    | IdentifyValue(str, value) -> printfn "%s %f" str value
    | _ -> ()

This would produce:

Circle Characteristics
----------------------
Radius:   35.85
Diameter: 71.70

Press any key to continue . . .

Introduction to Exception Handling.NET

Introduction

Before the creation of the .NET Framework, many computer languages supported error or exception handling. These included C++ (with its try . . . catch(...) mechanism), Visual Basic (with its On Error GoTo ... mechanism), etc. This means that different languages used different techniques to address the issues related to errors. As those languages were imported to the .NET Framework, a unified technique was created. As a result, all languages of the .NET Framework must support exception handling and they practically use the same technique. To start, the .NET Framework includes various classes purposely made for exception handling.

The Exception Class

The most fundamental class that supports exception handling in the .NET Framework is called Exception. The class starts as follows:

type Exception =  
    class
    	abstract Message : string with get 
	override Message : string with get
	abstract Source : string with get, set 
	override Source : string with get, set
	abstract Data : IDictionary with get 
	override Data : IDictionary with get

	. . .

        interface ISerializable 
        interface _Exception 
    end

The Source property indicates what caused the error. The source can be anything. It depends on the type of error and what was going on. One of the most important characteristics of the Exception class is a property named Message. Like the name indicates, this property holds a description of the error that was produced from the action. In reality, both the source and the message from the Exception class are vague and could mean anything in different circumstances. Normally, when handling an exception in your program, you are free to indicate the source of the problem and to provide a message to display. To let you get more information about the error, the Exception class is equipped with a property named Data.

As mentioned above, the Exception class is vague. It mostly serves as a foundation for other classes. In fact, if you want to create a customized way to handle exceptions in your program, you can create a class that derives from Exeption.

The FormatException Class

Instead of creating your own classes to handle exceptions, the .NET Framework provides many good classes for various situations. There are classes to address issues related to numbers, files, pictures, music, databases, etc.

The FormatException class is mostly used to validate numeric values that are provided in a program. If a value is valid, there is no exception. If there is a problem, such as if a string is provided where a number is expected, a FormatException is thrown.

The DivideByZeroException Class

The DivideByZeroException class is used when there is an attempt to divide a number by 0.

The OverflowException Class

The OverflowException class is used when the value provided to (or sorted in) a variable is larger than the memory area reserved for that value. An example is when a variable made to hold values from 0 to 255 is asked to store a value higher than that.

The ArgumentOutOfRangeException Class

The ArgumentOutOfRangeException class is used if the compiler cannot convert a value from one type into another; that is, if the conversion fails, for any reason

Resources Cleaning Up

Introduction

Some applications or parts of an application use a lot of resources. Examples of resources are hard drives, servers, communication media, etc. For example, an application may be asked to locate and use files on a computer. Another application may have to create a connection to a database on a remote computer. Sometimes, these types of operations require that an application monopolyze a resource and other applications must wait. For example, while one application opens and is using a file, other applications may be prevented from using or accessing that same file. When the first application has finished using the resource, it must stop using the resource so that other applications that need the same resource can use it. To help you take care of this, the F# language provides the finally keyword. The formula to use it is:

try
    statement(s)
finally
    body

You start with a try section exactly as we reviewed for the regular exception handling. In the finally section, create the code that would free the resources. Normally, the finally section is not used for regular exception handling; it is used to free resources that were used in a program. If you want to handle an exception, create the appropriate section in the try block. The formula to follow is:

try
    try
        statement(s)
    with
        exception-handler
finally
    body

Cleaning-Up After Using a Resource

The finally option requires a few steps that include an explicit free-up of resources after using them. The .NET Framework provides a better mechanism through an interface named IDispose. This interface has only one method, named Dispose. This method takes no argument and returns nothing. The actual role of this method is to clean-up resources that an application was using.

To clean up resources, you must use a class that implements the IDispose interface. Of course, you can create your own class that implements this interface. In reality, all of the classes used in scenarios that consume resources implement the IDispose interface. When using one of those classes, to assist you with freeing the resources, the F# language provides the use keyword that is used as an operator. The formula to use it is as follows:

use variable = method

The use keyword is followed by a variable of the class that will be used to consume resources. On the right side of =, call a method that will be used to actually consume the resources. When the method has finished doing its work, the compiler calls the IDispose.Dispose() method to free the resources that were used. An alternative is the one used by other languages. This technique uses a function named using. The formula to use it is:

using(Parameter) body

The using() function takes one argument that specifies what method will consume the resources. The body of this function follows the = sign. If the body is short enough, it can be written on the same line that calls the using() function. If the body involves many lines, it should be created on the next line. In this case, the body should be indented. At the end of the body of the function, the compiler will call the IDispose.Dispose() method to free the resources that were used.


Previous Copyright © 2014-2024, FunctionX Monday 14 February 2022 Next