Introduction to Exceptions

Overview

So far (in previous lessons), we have learned to perform various types of operations that involve numeric values. What we did so far was to avoid bad occurences. The reality is that bad occurrences are a reality of life. We need to learn how to deal with them so we can assist the users of our application when an error occurs in our application.

Practical LearningPractical Learning: Introducing Exception Handling

  1. Start Microsoft Visual Studio. Create a new Console App named PledgeDistribution1 that uses the .NET 7.0 (Standard Term Support)
  2. In the Solution Explorer, right-click Program.cs and click Rename
  3. Type FundRaising (to get FundRaising.cs) and press Enter twice
  4. Change the document as follows:
    using static System.Console;
    
    int DistributeFirstPart(int amount, int portion1, int portion2)
    {
        int totalRatios = portion1 + portion2;
        int eachPart = amount / totalRatios;
        int result = eachPart * portion1;
    
        return result;
    }
    
    int Subtract(int first, int second)
    {
        return first - second;
    }
    
    int amtPledged = 0;
    int ratio1 = 0;
    int ratio2 = 0;
    int part1 = 0;
    int part2 = 0;
    
    WriteLine("===================================================");
    WriteLine("Pledge Distribution");
    WriteLine("===================================================");
    WriteLine("Type the values to evaluate a pledge distribution");
    Write("Amount to Allocate:  ");
    amtPledged = int.Parse(ReadLine()!);
    Write("Portion 1:           ");
    ratio1 = int.Parse(ReadLine()!);
    Write("Portion 2:           ");
    ratio2 = int.Parse(ReadLine()!);
    
    part1 = DistributeFirstPart(amtPledged, ratio1, ratio2);
    part2 = Subtract(amtPledged, part1);
    
    WriteLine("===================================================");
    WriteLine("Pledge Distribution");
    WriteLine("===================================================");
    WriteLine($"Amouont Allocated:   {amtPledged}");
    WriteLine($"Portion 1:           {ratio1}");
    WriteLine($"Portion 2:           {ratio2}");
    WriteLine("---------------------------------------------------");
    WriteLine($"Part 1:              {part1}");
    WriteLine($"Part 2:              {part2}");
    WriteLine("===================================================");
  5. To execute the project, on the main menu, click Debug -> Start Without Debugging:
  6. When requested, type the Amount to Allocate 6450 and press Enter
  7. Type Portion 1 as 1 and press Enter
  8. Type Portion 2 as 1 and press Enter:
    ===================================================
    Pledge Distribution
    ===================================================
    Type the values to evaluate a pledge distribution
    Amount to Allocate:  6450
    Portion 1:           1
    Portion 2:           1
    ===================================================
    Pledge Distribution
    ===================================================
    Amouont Allocated:   6450
    Portion 1:           1
    Portion 2:           1
    ---------------------------------------------------
    Part 1:              3225
    Part 2:              3225
    ===================================================
    
    Press any key to close this window . . .
  9. To close the window, press Enter, and return to your programming environment

Exceptional Behaviors

An exception is an unusual situation that could occur in your project. As a programmer, you should anticipate any abnormal behavior that could be caused by the user entering wrong information that could otherwise lead to unpredictable results. The ability to deal with a program's eventual abnormal behavior is called exception handling. C# provides many keywords to deal with an exception:

  1. Trying the normal flow: To deal with the expected behavior of a program, use the try keyword as in the following formula:
    try {behavior}
    The try keyword is required. It lets the compiler know that you are attempting a normal flow of your program. The actual behavior that needs to be evaluated is included between an opening curly bracket "{" and a closing curly bracket "}". Inside the brackets, implement the normal flow that the program must follow, at least for this section of the code. Here is an example:
    double number, result;
    
    try 
    {
        number = double.Parse(ReadLine());
        result = number * 2.00;
    }
  2. Catching Errors: During the flow of the program as part of the try section, if an abnormal behavior occurs, instead of letting the webpage display an error, you can transfer the flow of the code to another section that can deal with it. The syntax used by this section is:
    catch {what-to-do}
    This section always follows the try section. There must not be any code between the try's closing bracket and the catch section. The catch keyword is required and follows the try section. Combined with the try block, the syntax of an exception would be:
    try
    {
        // Try the program flow
    }
    catch
    {
        // Catch the exception
    }

Practical LearningPractical Learning: Introducing Exceptional Behaviors

  1. To introduce exceptions, change the code as follows:
    using static System.Console;
    
    int DistributeFirstPart(int amount, int portion1, int portion2)
    {
        int totalRatios = portion1 + portion2;
        int eachPart = amount / totalRatios;
        int result = eachPart * portion1;
    
        return result;
    }
    
    int Subtract(int first, int second)
    {
        return first - second;
    }
    
    int amtPledged = 0;
    int ratio1 = 0;
    int ratio2 = 0;
    int part1 = 0;
    int part2 = 0;
    
    try
    {
        WriteLine("===================================================");
        WriteLine("Pledge Distribution");
        WriteLine("===================================================");
        WriteLine("Type the values to evaluate a pledge distribution");
        Write("Amount to Allocate:  ");
        amtPledged = int.Parse(ReadLine()!);
        Write("Portion 1:           ");
        ratio1 = int.Parse(ReadLine()!);
        Write("Portion 2:           ");
        ratio2 = int.Parse(ReadLine()!);
    
        part1 = DistributeFirstPart(amtPledged, ratio1, ratio2);
        part2 = Subtract(amtPledged, part1);
    
        WriteLine("===================================================");
        WriteLine("Pledge Distribution");
        WriteLine("===================================================");
        WriteLine($"Amouont Allocated:   {amtPledged}");
        WriteLine($"Portion 1:           {ratio1}");
        WriteLine($"Portion 2:           {ratio2}");
        WriteLine("---------------------------------------------------");
        WriteLine($"Part 1:              {part1}");
        WriteLine($"Part 2:              {part2}");
        WriteLine("===================================================");
    }
    catch
    {
    
    }
  2. To execute the application, on the main menu, click Debug -> Start Without Debugging
  3. Type the Amount Allocated as 1250000
  4. Press Enter twice
  5. To close the window, press Q, and return to your programming environment

Exceptions and Custom Messages

As mentioned already, if an error occurs when processing the code in the try section, the compiler transfers the processing to the next catch section. You can then use the catch section to deal with the error. At a minimum, you can display a message to inform the user. Here is an example:

double number, result;

try
{
    number = double.Parse(ReadLine());
    result = number * 2.00;
}
catch
{
    WriteLine("The value you entered is not a valid number.")
}

Practical LearningPractical Learning: Displaying Custom Messages

  1. To display custom messages to the visitor, change the code as follows:
    using static System.Console;
    
    int DistributeFirstPart(int amount, int portion1, int portion2)
    {
        int totalRatios = portion1 + portion2;
        int eachPart = amount / totalRatios;
        int result = eachPart * portion1;
    
        return result;
    }
    
    int Subtract(int first, int second)
    {
        return first - second;
    }
    
    int amtPledged = 0;
    int ratio1 = 0;
    int ratio2 = 0;
    int part1 = 0;
    int part2 = 0;
    
    try
    {
        WriteLine("===================================================");
        WriteLine("Pledge Distribution");
        WriteLine("===================================================");
        WriteLine("Type the values to evaluate a pledge distribution");
        Write("Amount to Allocate:  ");
        amtPledged = int.Parse(ReadLine()!);
        Write("Portion 1:           ");
        ratio1 = int.Parse(ReadLine()!);
        Write("Portion 2:           ");
        ratio2 = int.Parse(ReadLine()!);
        part1 = DistributeFirstPart(amtPledged, ratio1, ratio2);
        part2 = Subtract(amtPledged, part1);
    
        WriteLine("===================================================");
        WriteLine("Pledge Distribution");
        WriteLine("===================================================");
        WriteLine($"Amouont Allocated:   {amtPledged}");
        WriteLine($"Portion 1:           {ratio1}");
        WriteLine($"Portion 2:           {ratio2}");
        WriteLine("---------------------------------------------------");
        WriteLine($"Part 1:              {part1}");
        WriteLine($"Part 2:              {part2}");
        WriteLine("===================================================");
    }
    catch
    {
        WriteLine("The value you entered for the amount to allocate " +
                  "or pledge is not valid. Only natural numbers " +
                  "(such as 200, 1250, 5000, or 100000) are allowed.");
    }
  2. To execute the project, on the main menu, click Debug -> Start Without Debugging
  3. When requested, type the Amount to Allocated as Nothing and press Enter
    ===================================================
    Pledge Distribution
    ===================================================
    Type the values to evaluate a pledge distribution
    Amount to Allocate:  Nothing
    The value you entered for the amount to allocate or pledge is not valid. Only natural numbers (such as 200, 1250, 5000, or 100000) are allowed.
    
    Press any key to close this window . . .
  4. To close the window, press A, and return to your programming environment

Introduction to Exceptions in the .NET Library

The Exception Class

To support exception handling, the .NET Framework provides a special class called Exception. Once the compiler encounters an error, the Exception class allows you to identify the type of error and take an appropriate action.

Normally, Exception mostly serves as the general class of exceptions. Anticipating various types of problems that can occur in a program, Microsoft derived various classes from Exception to make this issue friendlier. As a result, almost any type of exception you may encounter already has a class created to deal with it.

In exception handling, errors are dealt with in the catch section. Therefore, use catch as if it were a method. This means that, on the right side of catch, add some parentheses. In the parentheses, pass a parameter of the type of exception you want to deal with. By default, an exception is first of type Exception. Based on this, a typical formula to implement exception handling is:

try
{
	// Process the normal flow of the program here
}
catch(Exception e)
{
	// Deal with the exception here
}

The Message of an Exception

When an exception occurs in the try section, code compilation is transferred to the catch section. If you pass the exception as an Exception type, this class will identify the error. One of the properties of the Exception class is called Message. This property is of type string. It describes the type of error that occurred. You can then use this Exception.Message property to display an error message if you want.

Practical LearningPractical Learning: Introducing the Exception Class

  1. Change the code as follows:
    using static System.Console;
    
    int DistributeFirstPart(int amount, int portion1, int portion2)
    {
        int totalRatios = portion1 + portion2;
        int eachPart = amount / totalRatios;
        int result = eachPart * portion1;
    
        return result;
    }
    
    int Subtract(int first, int second)
    {
        return first - second;
    }
    
    int amtPledged = 0;
    int ratio1 = 0;
    int ratio2 = 0;
    int part1 = 0;
    int part2 = 0;
    
    try
    {
        WriteLine("===================================================");
        WriteLine("Pledge Distribution");
        WriteLine("===================================================");
        WriteLine("Type the values to evaluate a pledge distribution");
        Write("Amount to Allocate:  ");
        amtPledged = int.Parse(ReadLine()!);
        Write("Portion 1:           ");
        ratio1 = int.Parse(ReadLine()!);
        Write("Portion 2:           ");
        ratio2 = int.Parse(ReadLine()!);
    
        part1 = DistributeFirstPart(amtPledged, ratio1, ratio2);
        part2 = Subtract(amtPledged, part1);
    
        WriteLine("===================================================");
        WriteLine("Pledge Distribution");
        WriteLine("===================================================");
        WriteLine($"Amouont Allocated:   {amtPledged}");
        WriteLine($"Portion 1:           {ratio1}");
        WriteLine($"Portion 2:           {ratio2}");
        WriteLine("---------------------------------------------------");
        WriteLine($"Part 1:              {part1}");
        WriteLine($"Part 2:              {part2}");
        WriteLine("===================================================");
    }
    catch(Exception exc)
    {
        WriteLine(exc.Message);
    }
  2. To execute the project, on the main menu, click Debug -> Start Without Debugging
  3. When requested, type the Amount to Allocated as I am giving and press Enter
    ===================================================
    Pledge Distribution
    ===================================================
    Type the values to evaluate a pledge distribution
    Amount to Allocate:  I am giving
    Input string was not in a correct format.
    
    Press any key to close this window . . .
  4. To close the window, press Z, and return to your programming environment

Introduction to Custom Error Messages

One of the strengths of the Exception.Message property is that it gives you a good indication of the type of problem that occurred. Sometimes, the message provided by the Exception class may not appear explicit enough. In fact, you may not want to show it to the visitor because, in some cases, the user may not understand what the expression "correct format" means and why it is being used. As an alternative, you can create your own message and display it to the user. One way to do this is to create a message box in a catch clause. Here is an example:

double number, result;

try {
    number = double.Parse(ReadLine());
    result = number * 2.00;
}
catch(Exception exc)
{
    WriteLine("The operation could not be carried because " +
                    "the number you typed is not valid";
}

If you use the above technique, in some cases, the C# compiler, or some C# compilers, may present a warning because you didn't use the argument. To address this issue, you have various options. You can pass the parameter as an underscore and not use that underscore in the catch section (but you may receive the same warning as previously). Here is an example:

void Calculate()
{
    try
    {
    }
    catch(Exception _)
    {
        WriteLine(" The value you entered for the amount to allocate " +
                        "or pledge is not valid. Only natural numbers " +
                        "(such as 200, 1250, 5000, or 100000) are allowed.");
    }
}

You can use only the message of the argument as seen earlier:

void Calculate()
{
    try
    {
    }
    catch(Exception exc)
    {
        WriteLine(exc.Message);
    }
}

You can combine the Exception.Message message and your own message. Here is an example:

void Calculate()
{
    try
    {
    }
    catch(Exception exc)
    {
        WriteLine(exc.Message +
                        " The value you entered for the amount to allocate " +
                        "or pledge is not valid. Only natural numbers " +
                        "(such as 200, 1250, 5000, or 100000) are allowed.");
    }
}

The last, sometimes the best, solution is to not name the parameter. Here is an example:

void Calculate()
{
    try
    {
    }
    catch(Exception)
    {
        WriteLine(" The value you entered for the amount to allocate " +
                        "or pledge is not valid. Only natural numbers " +
                        "(such as 200, 1250, 5000, or 100000) are allowed.");
    }
}

In some cases, you will want to create a custom error message but send it to another section of the project. To assist you with this, the Exception class with equipped with a constructor that takes a string as argument.

A Custom Exception Class

You can create your own class to deal with exceptions in your project, and you can create such a class from scratch. Instead of going through such a complicated adventure, a better alternative is derive a class from Exception. Here is an example:

public class UnwantedBehavior : Exception
{
}

By tradition, a class that derives from Exception must have a name that ends with Exception. Here is an example:

public class UnwantedBehaviorException : Exception
{
}

To assist you with exceptions, the .NET Framework provides a rich collection of classes for exceptions. Therefore, before creating your own exception class, first find out if there is already an exception class that provides the behavior you want, and use that built-in class.

Introduction to Built-In Exception Classes

Overview

The .NET Framework provides various classes to handle almost any type of exception. When your application produces an error, the compiler will display a message to notify the user.

To identify the primary class that deals with the exception, click the Details button.

There are two main ways you can use one of the classes of the .NET Framework. If you know that a particular exception will be produced, pass its name to a catch() clause. You don't have to name the argument. Then, in the catch section, display a custom message.

The Format Exception

Everything the user types into an application using the keyboard is primarily a string and you must convert it to the appropriate type before using it. When you request a specific type of value from the user, after the user has typed it and you decide to convert it to the appropriate type, if your conversion fails, the compiler produces an error. The error is of a class named FormatException.

Here is a program that deals with a FormatException exception:

double number, result;

try {
    WriteLine("Number:");
    number = double.Parse(Request[]);
    result = number * 2.00;
}
catch(FormatException exc)
{
    WriteLine("The value you entered is not a valid number.");
}

Practical LearningPractical Learning: Using the FormatException Class

  1. Change the code as follows:
    using static System.Console;
    
    int DistributeFirstPart(int amount, int portion1, int portion2)
    {
        int totalRatios = portion1 + portion2;
        int eachPart = amount / totalRatios;
        int result = eachPart * portion1;
    
        return result;
    }
    
    int Subtract(int first, int second)
    {
        return first - second;
    }
    
    int amtPledged;
    int ratio1;
    int ratio2;
    int part1;
    int part2;
    
    try
    {
        WriteLine("===================================================");
        WriteLine("Pledge Distribution");
        WriteLine("===================================================");
        WriteLine("Type the values to evaluate a pledge distribution");
        Write("Amount to Allocate:  ");
        amtPledged = int.Parse(ReadLine()!);
        Write("Portion 1:           ");
        ratio1 = int.Parse(ReadLine()!);
        Write("Portion 2:           ");
        ratio2 = int.Parse(ReadLine()!);
    
        part1 = DistributeFirstPart(amtPledged, ratio1, ratio2);
        part2 = Subtract(amtPledged, part1);
    
        WriteLine("===================================================");
        WriteLine("Pledge Distribution");
        WriteLine("===================================================");
        WriteLine($"Amouont Allocated:   {amtPledged}");
        WriteLine($"Portion 1:           {ratio1}");
        WriteLine($"Portion 2:           {ratio2}");
        WriteLine("---------------------------------------------------");
        WriteLine($"Part 1:              {part1}");
        WriteLine($"Part 2:              {part2}");
        WriteLine("===================================================");
    }
    catch (FormatException)
    {
        WriteLine("The value you entered for the amount to allocate " +
                  "or pledge is not valid. Only natural numbers " +
                  "(such as 200, 1250, 5000, or 100000) are allowed.");
    }
  2. To execute the project, on the main menu, click Debug -> Start Without Debugging
  3. For the Amount to Allocate, type 6250 and press Enter
  4. For Portion 1, type 3 and press Enter
  5. For Portion 2, type 2 and press Enter
    ===================================================
    Pledge Distribution
    ===================================================
    Type the values to evaluate a pledge distribution
    Amount to Allocate:  6250
    Portion 1:           3
    Portion 2:           2
    ===================================================
    Pledge Distribution
    ===================================================
    Amouont Allocated:   6250
    Portion 1:           3
    Portion 2:           2
    ---------------------------------------------------
    Part 1:              3750
    Part 2:              2500
    ===================================================
    
    Press any key to close this window . . .
  6. To close the window, press Z, and return to your programming environment
  7. Change the document as follows:
    using static System.Console;
    
    (int, int) DistributeParts(int amount, int portion1, int portion2)
    {
        int totalRatios = portion1 + portion2;
        int eachPart    = amount / totalRatios;
        int result      = eachPart * portion1;
    
        return (result, amount - result);
    }
    
    int Subtract(int first, int second)
    {
        return first - second;
    }
    
    int amtPledged = 0;
    int ratio1 = 0;
    int ratio2 = 0;
    
    try
    {
        WriteLine("===================================================");
        WriteLine("Pledge Distribution");
        WriteLine("===================================================");
        WriteLine("Type the values to evaluate a pledge distribution");
        Write("Amount to Allocate:  ");
        amtPledged = int.Parse(ReadLine()!);
        Write("Portion 1:           ");
        ratio1 = int.Parse(ReadLine()!);
        Write("Portion 2:           ");
        ratio2 = int.Parse(ReadLine()!);
    
        (int first, int second) portions = DistributeParts(amtPledged, ratio1, ratio2);
    
        WriteLine("===================================================");
        WriteLine("Pledge Distribution");
        WriteLine("===================================================");
        WriteLine($"Amouont Allocated:   {amtPledged}");
        WriteLine($"Portion 1:           {ratio1}");
        WriteLine($"Portion 2:           {ratio2}");
        WriteLine("---------------------------------------------------");
        WriteLine($"Part 1:              {portions.first}");
        WriteLine($"Part 2:              {portions.second}");
        WriteLine("===================================================");
    }
    catch (FormatException)
    {
        WriteLine("The value you entered for the amount to allocate " +
                  "or pledge is not valid. Only natural numbers " +
                  "(such as 200, 1250, 5000, or 100000) are allowed.");
    }
  8. To execute the project, on the main menu, click Debug -> Start Without Debugging
  9. For the Amount to Allocate, type 8500 and press Enter
  10. For Portion 1, type 4 and press Enter
  11. For Portion 2, type 3 and press Enter
    ===================================================
    Pledge Distribution
    ===================================================
    Type the values to evaluate a pledge distribution
    Amount to Allocate:  8500
    Portion 1:           4
    Portion 2:           3
    ===================================================
    Pledge Distribution
    ===================================================
    Amouont Allocated:   8500
    Portion 1:           4
    Portion 2:           3
    ---------------------------------------------------
    Part 1:              4856
    Part 2:              3644
    ===================================================
    
    Press any key to close this window . . .
  12. Press W to close the window and return to your programming environment

The Overflow Exception

An application receives, processes, and produces values on a regular basis. To better manage these values, as we saw when studying variables and data types in previous lessons, the compiler uses appropriate amounts of space to store its values. If you provide a value higher than the data type allows, you would get an error.

When a value beyond the allowable range is asked to be stored in memory, the compiler produces an error of a class named OverflowException.

The Argument Out of Range Exception

If the user provides a value that is outside a range allowed by its type or class and therefore cannot be converted appropriately, the compiler produces an exception of a class named ArgumentOutOfRangeException.

The Divide by Zero Exception

If your application tries to divide a number by 0, the compiler produces an error of a class named DivideByZeroException.

Throwing an Exception

Introduction

As mentioned previously, the Exception class is equipped with a Message property that carries a message for the error that occurred. We also mentioned that the message of this property may not be particularly useful to a user. Fortunately, you can create your own message and pass it to an Exception object. To be able to receive custom messages, the Exception class provides the following constructor:

public Exception(string message);

As mentioned already, all exceptions classes derive directly or indirectly from Exception. As a result, all those classes are equipped with a default constructor and all of them inherit the constructor that takes a string as argument.

To use the constructor that uses a string parameter, in the section in which you are anticipating the error, use a keyword named throw. Follow this keyword by the new operator to instantiate the Exception class using this constructor. Here is an example:

using static System.Console;

double result = 0.00;
string oper = "";
double number1 = 0.00, number2 = 0.00;

try
{
    Write("Number 1: ");
    number1 = double.Parse(ReadLine()!);
    Write("Operator: ");
    oper = ReadLine()!;
    Write("Number 2: ");
    number2 = double.Parse(ReadLine()!);

    if ((oper != "+") && (oper != "-") && (oper != "*") && (oper != "/"))
    {
        throw new Exception(oper);
    }

    switch (oper)
    {
        case "+":
            result = number1 + number2;
            break;
        case "-":
            result = number1 - number2;
            break;
        case "*":
            result = number1 * number2;
            break;
        case "/":
            result = number1 / number2;
            break;
        default:
            WriteLine("Bad Operation");
            break;
    }

    WriteLine("-------------------------");
    WriteLine("{0} {1} {2} = {3}", number1, oper, number2, result);
}
catch (Exception exc)
{
    WriteLine("A value or operator you entered is not valid.");
}

WriteLine("================================");

Here is an example of running the application:

Number 1: 837
Operator: +
Number 2: 559
-------------------------
837 + 559 = 1396
================================

Press any key to close this window . . .

Exceptions and Methods

There are various ways you can involve exceptions in a function or method. When it comes to functions and methods, one way you can use the throw option is to get out of the function or method at any time, for any reason. Consider the following code:

class Exercise
{
    public void ShowMessage()
    {
        throw new Exception();

        WriteLine("This is the wonderful world of C# programming.");
    }
}

In this example, the line that holds the message box will never be reached. One way you can proceed in a function or method is to throw an exception if something happens. That is, you can create a conditional statement that would consider, or throw an exception.

Practical LearningPractical Learning: Throwing an Exception from a Method

  1. Change the DistributeParts() method as follows:
    using static System.Console;
    
    (int, int) DistributeParts(int amount, int portion1, int portion2)
    {
        int totalRatios = portion1 + portion2;
    
        if (totalRatios == 0)
            throw new FormatException("The value provided for one or both ratios is not valid.");
    
        int eachPart    = amount / totalRatios;
        int result      = eachPart * portion1;
    
        return (result, amount - result);
    }
    
    int Subtract(int first, int second)
    {
        return first - second;
    }
    
    int amtPledged = 0;
    int ratio1 = 0;
    int ratio2 = 0;
    
    try
    {
        WriteLine("===================================================");
        WriteLine("Pledge Distribution");
        WriteLine("===================================================");
        WriteLine("Type the values to evaluate a pledge distribution");
        Write("Amount to Allocate:  ");
        amtPledged = int.Parse(ReadLine()!);
        Write("Portion 1:           ");
        ratio1 = int.Parse(ReadLine()!);
        Write("Portion 2:           ");
        ratio2 = int.Parse(ReadLine()!);
    
        (int first, int second) portions = DistributeParts(amtPledged, ratio1, ratio2);
    
        WriteLine("===================================================");
        WriteLine("Pledge Distribution");
        WriteLine("===================================================");
        WriteLine($"Amouont Allocated:   {amtPledged}");
        WriteLine($"Portion 1:           {ratio1}");
        WriteLine($"Portion 2:           {ratio2}");
        WriteLine("---------------------------------------------------");
        WriteLine($"Part 1:              {portions.first}");
        WriteLine($"Part 2:              {portions.second}");
        WriteLine("===================================================");
    }
    catch (FormatException)
    {
        WriteLine("The value you entered for the amount to allocate " +
                  "or pledge is not valid. Only natural numbers " +
                  "(such as 200, 1250, 5000, or 100000) are allowed.");
    }
  2. To execute the project, on the main menu, click Debug -> Start Without Debugging
  3. For the Amount to Allocate, type 8500 and press Enter
  4. For Portion 1, type 0 and press Enter
  5. For Portion 2, type 0 and press Enter
    ===================================================
    Pledge Distribution
    ===================================================
    Type the values to evaluate a pledge distribution
    Amount to Allocate:  8500
    Portion 1:           0
    Portion 2:           0
    The value you entered for the amount to allocate or pledge is not valid. Only natural numbers (such as 200, 1250, 5000, or 100000) are allowed.
    
    Press any key to close this window . . .
  6. To close the window, press X, and return to your programming environment
  7. One of the characteristics of a function is that it returns a value, which is done using the return keyword. Therefore, if a calculation made in a function may produce an error, you can create a conditional statement, such as an if...else or if...else if...else scenario where the last else is a throw condition.
    For an example, change the document as follows:
    using static System.Console;
    
    int DistributeFirstPart(int amount, int portion1, int portion2)
    {
        if (portion1 != 0)
        {
            int totalRatios = portion1 + portion2;
    
            int eachPart = amount / totalRatios;
            int result = eachPart * portion1;
    
            return result;
        }
        else
           throw new FormatException("The value provided for the first ratio portion is not valid.");
    }
    
    int Subtract(int first, int second)
    {
        if (first == 0)
            throw new FormatException("The value provided for the amount to pledge must be a positive number.");
    
        return first - second;
    }
    
    int amtPledged = 0;
    int ratio1 = 0;
    int ratio2 = 0;
    
    try
    {
        WriteLine("===================================================");
        WriteLine("Pledge Distribution");
        WriteLine("===================================================");
        WriteLine("Type the values to evaluate a pledge distribution");
        Write("Amount to Allocate:  ");
        amtPledged = int.Parse(ReadLine()!);
        Write("Portion 1:           ");
        ratio1 = int.Parse(ReadLine()!);
        Write("Portion 2:           ");
        ratio2 = int.Parse(ReadLine()!);
    
        int part1 = DistributeFirstPart(amtPledged, ratio1, ratio2);
        int part2 = Subtract(amtPledged, part1);
    
        WriteLine("===================================================");
        WriteLine("Pledge Distribution");
        WriteLine("===================================================");
        WriteLine($"Amouont Allocated:   {amtPledged}");
        WriteLine($"Portion 1:           {ratio1}");
        WriteLine($"Portion 2:           {ratio2}");
        WriteLine("---------------------------------------------------");
        WriteLine($"Part 1:              {part1}");
        WriteLine($"Part 2:              {part2}");
        WriteLine("===================================================");
    }
    catch (FormatException)
    {
        WriteLine("The value you entered for the amount to allocate " +
                  "or pledge is not valid. Only natural numbers " +
                  "(such as 200, 1250, 5000, or 100000) are allowed.");
    }
  8. To execute the project, on the main menu, click Debug -> Start Without Debugging
  9. For the Amount to Allocate, type 5000 and press Enter
  10. For Portion 1, type 0 and press Enter
  11. For Portion 2, type 0 and press Enter:
    ===================================================
    Pledge Distribution
    ===================================================
    Type the values to evaluate a pledge distribution
    Amount to Allocate:  5000
    Portion 1:           0
    Portion 2:           0
    The value you entered for the amount to allocate or pledge is not valid. Only natural numbers (such as 200, 1250, 5000, or 100000) are allowed.
    
    Press any key to close this window . . .
  12. To close the window, press X, and return to your programming environment

Exceptions and Properties

If you create a non-automatic property that processes some data and returns a valid result, if there is a possibility that something could go wrong in the calculation, you can make the property throw an exception. You can perform some procesing in the set clause of the property. In that clause, you can throw an exception. Here is an example:

public class WorkDay
{
    private double tw;

    public WorkDay(double time)
    {
        tw = time;
    }

    public double TimeWorked
    {
        get
        {
            return tw;
        }
        set
        {
            if (tw > 24)
                throw new FormatException("Nobody can work more than 24 hours in one day.");

            tw = value;
        }
    }
}

Or you can handle the exception in the get clause.

Topics on Handling Exceptions

Catching Various Exceptions

In the above examples, when we anticipated some type of problem, we instructed the compiler to use our default catch section. We left it up to the compiler to find out when there was a problem and we provided a catch section to deal with it. A secttion of code with numerous or complex operations and requests can also produce different types of errors. You should be able to face different problems and deal with them individually, each by its own kind. To do this, you can create different catch sections, each made for a particular error. The formula to follow is:

try {
	// Code to Try
}
catch(Arg1)
{
	// One Exception
}
catch(Arg2)
{
	// Another Exception
}

The compiler would proceed from top-down:

  1. Following the normal flow of the program, the compiler enters the try block
  2. If no exception occurs in the try block, the rest of the try block executes.
    If an exception occurs in the try block, the compiler registers the type of error that occurred. If there is a throw line, the compiler registers it also:
    1. The compiler gets out of the try section
    2. The compiler examines the first catch section. If the first catch clause matches the thrown error, that catch section executes and the exception handling routine may end. If the first catch doesn't match the thrown error, the compiler proceeds with the next catch clause
    3. The compiler checks the next match, if any, and proceeds as in the first catch. This continues until the compiler finds a catch clause that matches the thrown error
    4. If one of the catch clauses matches the thrown error, its body executes. If no catch clause matches the thrown error, the compiler calls the Exception class and uses the default message

Multiple catch clauses are written if or when a try block is expected to throw different types of errors. For example, in our calculator, we want to consider only the addition, the subtraction, the multiplication, and the division. It is also likely that the user may type one or two invalid numbers. This leads us to know that our project can produce at least two types of errors. Based on this, we can address them using two catch clauses.

Obviously various bad things could happen when a form is used. Imagine that the user wants to perform a division. You need to tell the webpage what to do if the user enters the denominator as 0 (or 0.00). If this happens, one of the options you should consider is to display a message and get out. Fortunately, the .NET Framework provides the DivideByZeroException class to which we were introduced in the previous lesson to deal with an exception caused by division by zero. As done with the message passed to the Exception class, you can compose your own message and pass it to the DivideByZeroException(string message) constructor.

Here is an example:

using static System.Console;

double result = 0.00;
string oper = "";
double number1 = 0.00, number2 = 0.00;

try
{
    Write("Number 1: ");
    number1 = double.Parse(ReadLine()!);
    Write("Operator: ");
    oper = ReadLine()!;
    Write("Number 2: ");
    number2 = double.Parse(ReadLine()!);

    if ((oper != "+") && (oper != "-") && (oper != "*") && (oper != "/"))
    {
        throw new Exception(oper);
    }

    switch (oper)
    {
        case "+":
            result = number1 + number2;
            break;
        case "-":
            result = number1 - number2;
            break;
        case "*":
            result = number1 * number2;
            break;
        case "/":
            result = number1 / number2;
            break;
        default:
            WriteLine("Bad Operation");
            break;
    }

    WriteLine("-------------------------");
    WriteLine("{0} {1} {2} = {3}", number1, oper, number2, result);
}
catch (FormatException)
{
    WriteLine("You type an invalid number. Please correct it");
}
catch (Exception ex)
{
    WriteLine("Operation Error: " + ex.Message + " is not a valid operator");
}

WriteLine("================================");

Here is an example of running the application:

umber 1: 862
Operator: -
Number 2: 48504
-------------------------
862 - 48504 = -47642
================================

Press any key to close this window . . .

Exception Filters

Sometimes, in your application, you will have different sections that may throw the same type of exception. The problem is that these sections may present the same error message. For example, if you have more than two sections that expect numeric values, if a valid value is not provided, each of the sections would throw a FormatException exception. The problem is that when one of those sections throws the exception, you may need a way to either know what section threw the exception or what exception was thrown.

Filtering an exception consists of specifying a criterion by which a particular exception, among others, would be selected. The C# language provides a mechanism that allows you to get more details about an exception. To start, you must use a technique that would be applied to each particular error. One way to do this is to use the throw keyword to create a custom message when an exception is thrown. To help you select an exception, the C# language provides the when keyword. The formula to use it is:

try
{
	. . .
}
catch(exception-parameter) when (condition)
{
	. . .
}

In the catch clause, pass an exception-parameter as we have done so far. This is followed by the when keyword that is used as in an if() conditional statement. In its parentheses, provide a logical expression that can evaluate to true or false. If that expression is true, the body of the catch clause would execute.

Nesting an Exception

You can create an exception inside of another. This is referred to as nesting an exception. This is done by applying the same techniques we used to nest conditional statements. This means that you can write an exception that depends on, and is subject to, another exception. To nest an exception, write a try block in the body of the parent exception. The nested try block must be followed by its own catch clause(s). To effectively handle the exception(s), make sure you include an appropriate throw in the try block.

Here is an example:

using static System.Console;

double result = 0.00;
string oper = "";
double number1 = 0.00, number2 = 0.00;

try
{
    Write("Number 1: ");
    number1 = double.Parse(ReadLine()!);
    Write("Operator: ");
    oper = ReadLine()!;
    Write("Number 2: ");
    number2 = double.Parse(ReadLine()!);

    if ((oper != "+") && (oper != "-") && (oper != "*") && (oper != "/"))
    {
        throw new Exception(oper);
    }

    switch (oper)
    {
        case "+":
            result = number1 + number2;
            break;
        case "-":
            result = number1 - number2;
            break;
        case "*":
            result = number1 * number2;
            break;
        case "/":
            try
            {
                if (number2 == 0)
                    throw new DivideByZeroException("Division by zero is not allowed");

                result = number1 / number2;
            }
            catch (DivideByZeroException ex)
            {
                WriteLine(ex.Message);
            }
            break;
        default:
            WriteLine("Bad Operation");
            break;
    }

    WriteLine("-----------------------------------");
    WriteLine("{0} {1} {2} = {3}", number1, oper, number2, result);
}
catch (FormatException)
{
    WriteLine("You type an invalid number. Please correct it");
}
catch (Exception ex)
{
    WriteLine("Operation Error: " + ex.Message + " is not a valid operator");
}

WriteLine("===================================");

Here is an example of running the application:

Number 1: 9284259
Operator: /
Number 2: 731
-----------------------------------
9284259 / 731 = 12700.764705882353
===================================

Press any key to close this window . . .

Practical LearningPractical Learning: Handling Exceptions

  1. Start a new Console App that supports .NET 7.0 (Standard Term Support) and named StellarWaterPoint4
  2. To create a class, in the Solution Explorer, right-click StellarWaterPoint4 -> Add -> Class...
  3. Type WaterBill as the name of the file and class
  4. Click Add
  5. Change the class as follows:
    namespace StellarWaterPoint3
    {
        internal enum Classification { Residential, NonProfit, WaterIntensive, GeneralUse, Other }
    
        internal class WaterBill
        {
            private readonly string typeOfAccount;
    
            public WaterBill(string category) => typeOfAccount = category;
    
            public double CounterReadingStart { get; set; }
            public double CounterReadingEnd   { get; set; }
    
            public (Classification @class, string name) AccountType
            {
                get
                {
                    if (typeOfAccount == "1")
                        return (Classification.Residential, "Residential Household");
                    else if (typeOfAccount == "2")
                        return (Classification.NonProfit, "Social/Non-Profit Organization");
                    else if (typeOfAccount == "3")
                        return (Classification.WaterIntensive, "Water Intensive Business (Laudromat, Hair Salon, etc)");
                    else if (typeOfAccount == "4")
                        return (Classification.GeneralUse, "General Business, Government");
                    else
                        return (Classification.Other, "Other");
                }
            }
    
            public double Gallons => CounterReadingEnd - CounterReadingStart;
    
            public double HCFTotal => Gallons / 748;
    
            public (double First25Therms, double Next15Therms, double Above40Therms) Therms
            {
                get
                {
                    double first, next, above;
    
                    if (HCFTotal is > 40.00)
                    {
                        first = 25.00 * 1.5573;
                        next  = 15.00 * 1.2264;
                        above = (HCFTotal - 40.00) * 0.9624;
                    }
                    else if (HCFTotal > 25.00)
                    {
                        first = 25.00 * 1.5573;
                        next  = (HCFTotal - 25.00) * 1.2264;
                        above = 0.00;
                    }
                    else // if (HCFTotal > 40.00)
                    {
                        first = HCFTotal * 1.5573;
                        next  = 0.00;
                        above = 0.00;
                    }
    
                    return (first, next, above);
                }
            }
    
            public double WaterUsageCharges => Therms.First25Therms + Therms.Next15Therms + Therms.Above40Therms;
    
            public double SewerCharges
            {
                get
                {
                    double result = AccountType.@class switch
                    {
                        Classification.Residential     => WaterUsageCharges * 0.2528,
                        Classification.NonProfit       => WaterUsageCharges * 0.0915,
                        Classification.WaterIntensive  => WaterUsageCharges * 1.5865,
                        Classification.GeneralUse      => WaterUsageCharges * 0.6405,
                        _                              => WaterUsageCharges * 1.2125
                    };
    
                    return result;
                }
            }
    
            public double EnvironmentCharges
            {
                get
                {
                    double result = AccountType.@class switch
                    {
                        Classification.Residential    => WaterUsageCharges * 0.0025,
                        Classification.NonProfit      => WaterUsageCharges * 0.0015,
                        Classification.WaterIntensive => WaterUsageCharges * 0.0105,
                        Classification.GeneralUse     => WaterUsageCharges * 0.0045,
                        _                             => WaterUsageCharges * 0.0125
                    };
    
                    return result;
                }
            }
    
            public double ServiceCharges
            {
                get => AccountType.@class switch
                {
                    Classification.Residential        => WaterUsageCharges * 0.0328,
                    Classification.NonProfit          => WaterUsageCharges * 0.0115,
                    Classification.WaterIntensive     => WaterUsageCharges * 0.1015,
                    Classification.GeneralUse         => WaterUsageCharges * 0.0808,
                    _                                 => WaterUsageCharges * 0.1164
                };
            }
    
            public double TotalCharges => WaterUsageCharges + SewerCharges + EnvironmentCharges + ServiceCharges;
    
            public double LocalTaxes
            {
                get => AccountType.@class switch
                {
                    Classification.Residential        => TotalCharges * 0.115,
                    Classification.NonProfit          => TotalCharges * 0.085,
                    Classification.WaterIntensive     => TotalCharges * 0.825,
                    Classification.GeneralUse         => TotalCharges * 0.315,
                    _                                 => TotalCharges * 0.625
                };
            }
    
            public double StateTaxes => AccountType.@class switch
            {
                Classification.Residential            => TotalCharges * 0.035,
                Classification.NonProfit              => TotalCharges * 0.015,
                Classification.WaterIntensive         => TotalCharges * 0.105,
                Classification.GeneralUse             => TotalCharges * 0.065,
                _                                     => TotalCharges * 0.815
            }; 
    
            public double AmountDue => TotalCharges + LocalTaxes + StateTaxes;
        }
    }
  6. Click the Program tab and change the document as follows:
    using StellarWaterPoint3;
    using static System.Console;
    
    WriteLine("Stellar Water Point");
    WriteLine("===========================================================");
    
    string ? answer = "";
    WaterBill ? bill;
    
    try
    {
        WriteLine("To prepare an invoice, enter the following information");
        WriteLine("Types of Accounts");
        WriteLine("1. Residential Household");
        WriteLine("2. Social/Non-Profit Organization");
        WriteLine("3. Water Intensive Business (Laudromat, etc)");
        WriteLine("4. General Business");
        WriteLine("0. Other");
        Write("Enter Type of Account:         ");
        answer = ReadLine()!;
        WriteLine("-----------------------------------------------------------");
    }
    catch(FormatException fe)
    {
        WriteLine("There was an error from the request for the account type was presented." + Environment.NewLine +
                  "The error produced was: " + fe.Message);
        WriteLine("-----------------------------------------------------------");
    }
    
    bill = new WaterBill(answer);
    
    try
    {
        Write("Counter Reading Start:         ");
        bill.CounterReadingStart = int.Parse(ReadLine()!);
    }
    catch (FormatException fe)
    {
        WriteLine("There was an error from the request for the counter starting number was made." + Environment.NewLine +
                  "The error produced was: " + fe.Message);
        WriteLine("-----------------------------------------------------------");
    }
    
    try
    {
        Write("Counter Reading End:           ");
        bill.CounterReadingEnd = int.Parse(ReadLine()!);
    }
    catch (FormatException fe)
    {
        WriteLine("There was an error from the request for the counter ending number was made." + Environment.NewLine +
                  "The error produced was: " + fe.Message);
        WriteLine("-----------------------------------------------------------");
    }
    
    if( (answer != "") && (bill.CounterReadingStart != 0) && (bill.CounterReadingEnd != 0))
    {
        WriteLine("===========================================================");
        WriteLine("Stellar Water Point - Customer Invoice");
        WriteLine("Account Type:               {0}", bill.AccountType.name);
        WriteLine("===========================================================");
        WriteLine("Meter Reading");
        WriteLine("-----------------------------------------------------------");
        WriteLine("Counter Reading Start:      {0,10}", bill.CounterReadingStart);
        WriteLine("Counter Reading End:        {0,10}", bill.CounterReadingEnd);
        WriteLine("Total Gallons Consumed:     {0,10}", bill.Gallons);
        WriteLine("===========================================================");
        WriteLine("Therms Evaluation");
        WriteLine("-----------------------------------------------------------");
        WriteLine("HCF Total:                  {0,10:n}", bill.HCFTotal);
        WriteLine("First 35 Therms (* 1.5573): {0,10:n}", bill.Therms.First25Therms);
        WriteLine("First 20 Therms (* 1.2264): {0,10:n}", bill.Therms.Next15Therms);
        WriteLine("Above 40 Therms (* 0.9624): {0,10:n}", bill.Therms.Above40Therms);
        WriteLine("-----------------------------------------------------------");
        WriteLine("Water Usage Charges:        {0,10:n}", bill.WaterUsageCharges);
        WriteLine("Sewer Charges:              {0,10:n}", bill.SewerCharges);
        WriteLine("===========================================================");
        WriteLine("Bill Values");
        WriteLine("-----------------------------------------------------------");
        WriteLine("Environment Charges:        {0,10:n}", bill.EnvironmentCharges);
        WriteLine("Service Charges:            {0,10:n}", bill.ServiceCharges);
        WriteLine("Total Charges:              {0,10:n}", bill.TotalCharges);
        WriteLine("Local Taxes:                {0,10:n}", bill.LocalTaxes);
        WriteLine("State Taxes:                {0,10:n}", bill.StateTaxes);
        WriteLine("-----------------------------------------------------------");
        WriteLine("Amount Due:                 {0,10:n}", bill.AmountDue);
    }
    else
    {
        Write("===========================================================");
        WriteLine("The water bill was not prepared appropriately.");
    }
    
    Write("===========================================================");
  7. To execute, on the main menu, click Debug -> Start Without Debugging
  8. When requested, for the type of account, don't type anything and simply press Enter
  9. For the Counter Reading Start, type 26526 and press Enter
  10. For the Counter Reading Start, type 42528 and press Enter
    Stellar Water Point
    ===========================================================
    To prepare an invoice, enter the following information
    Types of Accounts
    1. Residential Household
    2. Social/Non-Profit Organization
    3. Water Intensive Business (Laudromat, etc)
    4. General Business
    0. Other
    Enter Type of Account:
    -----------------------------------------------------------
    Counter Reading Start:         26526
    Counter Reading End:           42528
    ===========================================================
    The water bill was not prepared appropriately.
    ===========================================================
    
    Press any key to close this window . . .
  11. Close the window and return to your programming environment
  12. To execute again, on the main menu, click Debug -> Start Without Debugging
  13. When requested, for the type of account, type 1 and simply press Enter
  14. For the Counter Reading Start, type w and press Enter
  15. For the Counter Reading Start, type 42528 and press Enter
    Stellar Water Point
    ===========================================================
    To prepare an invoice, enter the following information
    Types of Accounts
    1. Residential Household
    2. Social/Non-Profit Organization
    3. Water Intensive Business (Laudromat, etc)
    4. General Business
    0. Other
    Enter Type of Account:         1
    -----------------------------------------------------------
    Counter Reading Start:         w
    There was an error from the request for the counter starting number was made.
    The error produced was: Input string was not in a correct format.
    -----------------------------------------------------------
    Counter Reading End:           42528
    ===========================================================
    The water bill was not prepared appropriately.
    ===========================================================
    Press any key to close this window . . .
  16. Close the window and return to your programming environment
  17. To execute again, on the main menu, click Debug -> Start Without Debugging
  18. When requested, for the type of account, type 1 and simply press Enter
  19. For the Counter Reading Start, type w and press Enter
  20. For the Counter Reading Start, type a and press Enter
    Stellar Water Point
    ===========================================================
    To prepare an invoice, enter the following information
    Types of Accounts
    1. Residential Household
    2. Social/Non-Profit Organization
    3. Water Intensive Business (Laudromat, etc)
    4. General Business
    0. Other
    Enter Type of Account:         1
    -----------------------------------------------------------
    Counter Reading Start:         w
    There was an error from the request for the counter starting number was made.
    The error produced was: Input string was not in a correct format.
    -----------------------------------------------------------
    Counter Reading End:           a
    There was an error from the request for the counter ending number was made.
    The error produced was: Input string was not in a correct format.
    -----------------------------------------------------------
    ===========================================================
    The water bill was not prepared appropriately.
    ===========================================================
    Press any key to close this window . . .
  21. Close the window and return to your programming environment
  22. To execute again, on the main menu, click Debug -> Start Without Debugging
  23. When requested, for the type of account, type 1 and simply press Enter
  24. For the Counter Reading Start, type 41722 and press Enter
  25. For the Counter Reading Start, type 88061 and press Enter
    Stellar Water Point
    ===========================================================
    To prepare an invoice, enter the following information
    Types of Accounts
    1. Residential Household
    2. Social/Non-Profit Organization
    3. Water Intensive Business (Laudromat, etc)
    4. General Business
    0. Other
    Enter Type of Account:         1
    -----------------------------------------------------------
    Counter Reading Start:         41722
    Counter Reading End:           88061
    ===========================================================
    Stellar Water Point - Customer Invoice
    Account Type:               Residential Household
    ===========================================================
    Meter Reading
    -----------------------------------------------------------
    Counter Reading Start:           41722
    Counter Reading End:             88061
    Total Gallons Consumed:          46339
    ===========================================================
    Therms Evaluation
    -----------------------------------------------------------
    HCF Total:                       61.95
    First 35 Therms (* 1.5573):      38.93
    First 20 Therms (* 1.2264):      18.40
    Above 40 Therms (* 0.9624):      21.13
    -----------------------------------------------------------
    Water Usage Charges:             78.45
    Sewer Charges:                   19.83
    ===========================================================
    Bill Values
    -----------------------------------------------------------
    Environment Charges:              0.20
    Service Charges:                  2.57
    Total Charges:                  101.06
    Local Taxes:                     11.62
    State Taxes:                      3.54
    -----------------------------------------------------------
    Amount Due:                     116.21
    ===========================================================
    Press any key to close this window . . .
  26. Close the window and return to your programming environment
  27. Close your programming environment

Previous Copyright © 2001-2023, FunctionX Sunday 05 November 2021 Next