Avoiding Errors with Exception and File Handling Programming C#
 
 

You can't predict every user income and what they will do with your program. For instance, you could ask a user for a number value and a character is entered. If you try to work with the character after expecting a number, errors occur in your program. To avoid these errors, you must be able to work with error and exception handling. These concepts avoid crashes in your program, and gracefully handle unforeseen input.

Another concept covered in this article is file handling. Not every program works with file input and output, but many auditing systems require output to either a database or a file system. You'll also need to understand how to work with file input from your users. We'll cover file handling and how you can work with opening, closing and reading files.

Error Handling

Error handling in C# is used with try, catch, finally and throw statements. These statements work together to trap an error, deal with it in a way that makes sense for your program, and then log an error or display it to the user. Not all errors require error output shown to the user. In some cases, you can manage the error with an alternative method and it doesn't interfere with the user's interaction with your program. In other cases, you're forced to tell the user that there was an error in the application. The user can then re-enter information, or you can close the application. Closing the application should only be done for extreme issues since this option is very frustrating for your users.

To illustrate error handling, let's go back to the program in Chapter 4 where we asked for user input from the console.

using System;

namespace WhatsMyInfo

{

    public class Program

    {

        public static void Main()

        {

            string name;

            int age;

            int birthyear;

            Console.Write("Please enter your name: ");

            name = Console.ReadLine();

            Console.Write("Please enter your age: ");

            age = Convert.ToInt32(Console.ReadLine());

            Console.Write("What year were you born?: ");

            birthyear = Convert.ToInt32(Console.ReadLine());

            //Print a blank line

            Console.WriteLine();

            //Show the details that the user entered

            Console.WriteLine("Name is {0}.", name);

            Console.WriteLine("Age is {0}.", age);

            Console.WriteLine("Birth year is {0}.", birthyear);

        }

    }

}

This program asks the user for a name, an age, and the year in which they were born. We mentioned in the chapter that an error would occur if the user entered a character for the age input since the input is stored to an integer variable. You don't want to just let the program crash if the wrong type of value is entered, so instead you need to perform error handling in the application.

The first part of error handling is the try statement. The try statement first tells the program to try a particular function. If it fails, the try statement then falls to the catch clause. This clause "catches" the error, so instead of throwing an error at your users the program allows you to perform alternative processes to handle the error.

Let's take a look at error handling with the above program.

using System;

namespace WhatsMyInfo

{

    public class Program

    {

        public static void Main()

        {

            string name;

            int age;

            int birthyear;

            Console.Write("Please enter your name: ");

            name = Console.ReadLine();

            try

            {

                Console.Write("Please enter your age: ");

                age = Convert.ToInt32(Console.ReadLine());

            } catch (Exception ex)

            {

                Console.Write("You entered an invalid age." + ex.Message);

                return;

            }

            Console.Write("What year were you born?: ");

            birthyear = Convert.ToInt32(Console.ReadLine());

            //Print a blank line

            Console.WriteLine();

            //Show the details that the user entered

            Console.WriteLine("Name is {0}.", name);

            Console.WriteLine("Age is {0}.", age);

            Console.WriteLine("Birth year is {0}.", birthyear);

        }

    }

}

Notice that we added a try-catch statement in our code. The try statement wraps the section of the code that asks the user for an age value. This value must be an integer. If the wrong value is entered, an error is thrown and the next code executed is within the catch statement. If the input is a valid integer, code execution skips the catch statement and continues with the next line of code outside of the try-catch block.

If an incorrect data type is entered, notice that we also handle the error by explaining to the user that an invalid age was entered. Your error message should be detailed enough that the user understands what they did wrong. In this case, the user entered an invalid age.

The catch block uses an Exception parameter. The Exception class captures the information logged by the operating system. The Exception class has several properties and methods you can use to get detailed information about the error. In this example, we print the main message to the screen. The message shows details of where the code threw an error and the reason for the error.

For the most part, printing the error message from the Exception class is not useful for your users. However, printing the error to the screen can be useful for the coder during testing or debugging the application.

Errors are tiered with inner messages. When you view the error message, you can step into it a tier further using the InnerMessage property. This message digs a bit deeper into the code to give you a more precise location for the error. In this sample program, it's easy to identify where the error occurred. However, when you have large applications with several try-catch statements, you'll need to view the inner and outer messages that give you more detail about the location and specifics of the error.

You can catch multiple exceptions at a time. For instance, suppose you want to get the user's average age for several years, but you accidentally divide by zero. Mathematically, this is invalid and the program will give you an error any time you try to divide by zero.

Let's take a look at error capturing for two exceptions.

using System;

namespace WhatsMyInfo

{

    public class Program

    {

        public static void Main()

        {

            string name;

            int age;

            int birthyear;

            Console.Write("Please enter your name: ");

            name = Console.ReadLine();

            try

            {

                Console.Write("Please enter your age: ");

                age = Convert.ToInt32(Console.ReadLine());

                age = age / 0;

            } catch (Exception ex)

            {

                Console.Write("You entered an invalid age." + ex.Message);

                return;

            }catch (DivideByZeroException ex)

            {

                Console.Write("You tried to divide by 0." + ex.Message);

                return;

            }            Console.Write("What year were you born?: ");

            birthyear = Convert.ToInt32(Console.ReadLine());

            //Print a blank line

            Console.WriteLine();

            //Show the details that the user entered

            Console.WriteLine("Name is {0}.", name);

            Console.WriteLine("Age is {0}.", age);

            Console.WriteLine("Birth year is {0}.", birthyear);

        }

    }

}

We added a new exception to the catch blocks. We now catch a divide-by-zero error. You obviously wouldn't hard code a division operation where you divide by zero, but this code is a good test for our new error exception handling. If the user enters an invalid age, the code automatically jumps to the catch block where we tell the user that an invalid age value was entered.

Let's assume the user enters a valid age. The next statement divides the age by 0, which is a mathematical error. The Exception library contains the DivideByZeroException class that handles this type of error. The code then jumps to this block and shows the error to the user.

The try-catch block also gives you the "finally" option. It's not a required block when you work with error handling, but it gives you the ability to execute final statements before ending the program or sending the user to another part of your program.

For instance, suppose you want to work with a file and opening the file results in an error. You need to close the file whether the read attempt is successful or not. The finally statement lets you close the file whether your program throws an error or the read attempt was successful.

Let's use this feature with our user information program. Suppose you want to send a message to the user before you stop the program. You want to give the user a warning that the age must be entered correctly, but regardless of successful input, you want to display a message that thanks the user for entering information.

The following code shows you how to use the finally statement to execute a final block of code.

using System;

namespace WhatsMyInfo

{

    public class Program

    {

        public static void Main()

        {

            string name;

            int age;

            int birthyear;

            Console.Write("Please enter your name: ");

            name = Console.ReadLine();

            try

            {

                Console.Write("Please enter your age: ");

                age = Convert.ToInt32(Console.ReadLine());

                age = age / 0;

            } catch (Exception ex)

            {

                Console.Write("You entered an invalid age." + ex.Message);

                return;

            }catch (DivideByZeroException ex)

            {

                Console.Write("You tried to divide by 0." + ex.Message);

                return;

            } finally

            {

                Console.Write ("Thank you for entering your information.");

            }

            Console.Write("What year were you born?: ");

            birthyear = Convert.ToInt32(Console.ReadLine());

            //Print a blank line

            Console.WriteLine();

            //Show the details that the user entered

            Console.WriteLine("Name is {0}.", name);

            Console.WriteLine("Age is {0}.", age);

            Console.WriteLine("Birth year is {0}.", birthyear);

        }

    }

}

Notice that we added the finally clause to our code. This is the final step whether the code has any errors or just executes normally. In this example, the user would see "Thank you for entering your information" regardless of an error or not.

File Handling

In the last section, we covered error handling, but you also need to know file handling to deal with opening and closing files. The C# library has several classes that help you work with files, so you don't need to make your own.

Before you can use the file classes, you need to import the IO namespace. The following line of code is needed at the top of each C# file that uses file classes.

using System.IO;

With the System.IO namespace including in your code, you can now practice with file handling methods.

First, you need to create a file before you can read from it. Of course, if the file already exists then you don't need to create it first. The File class lets you create a file and even overwrite it if it already exists. Let's take a look at the start of our program named MyFileProgram.

using System.IO;

public class MyFileProgram

{

    public MyFileProgram () {}

    public void CreateFile()

    {

        File.Create("C:\\file.txt").Close();

    }

}

In the above code, we created a new class named MyFileProgram. We created the constructor just like we did with other program classes. We then created a class member function named CreateFile. This method is meant to create the file before we open and read it.

The File class is static, so you don't need to instantiate it. The class only requires one line of code to create a file. In this example, we create a file on the C drive named file.txt. Notice that we use double backslashes to indicate the location of the file. A single backslash is an escape character in C, so just remember that you always need the double backslash when using a file location in a string.

With the file created, we can now write some data to it. Let's add a WriteFile class.

using System.IO;

public class MyFileProgram

{

    public MyFileProgram () {}

    public void CreateFile()

    {

        File.Create("C:\\file.txt").Close();

    }

    public void WriteFile()

    {

        FileStream F = new FileStream("c:\\file.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite);

         for (int i = 1; i <= 20; i++)

         {

            F.WriteByte((byte)i);

         }

         F.Close();

    }

}

The MyFileProgram now has a WriteFile class that opens the file, stores it in a FileStream variable, and then writes the numbers 1 to 20 to the file.

The FileStream class requires a file name and location, and what you plan to do with the file. The FileMode.OpenOrCreate parameter tells the FileStream class to open the file if it exists. If it doesn't, then create it. This ensures that we don't accidentally try to open and write to a non-existing file stream. You can tell the FileStream class to create, open, append or truncate a file as well.

The third parameter tells the FileStream class if you want to read or write to the file. In this case, we use the combined ReadWrite option that lets us use both read and write processes. This is required by the operating system. You must tell the operating system if you want to read or write to a file, and if you try to perform the wrong operation, the program will give you an error.

When you write to a file, you write in byte streams. Since we only write one number to the file, we can use the explicit conversion from integer value to byte value. More complex programs use byte streams. Data is converted to byte arrays and then transferred to opened files.

After we're done writing data to the file, we need to close it. One of the biggest mistakes when working with files is forgetting to close the file. When you open a file, the operating system puts a lock on it so that it can't be used by any other processes. If you forget to close the file and close your program instead, the operating system doesn't release the lock and you're forced to reboot the computer before you can gain access to the file again. If your program has an error while the file is opened and crashes, the lock is never released. This is where error handling helps as you program and test your code.

With the file created and data written to it, we need one last method to read the data from the file and print it out on the screen. Let's create a ReadFile method and add it to our MyFileProgram class.

using System.IO;

public class MyFileProgram

{

    public MyFileProgram () {}

    public void CreateFile()

    {

        File.Create("C:\\file.txt").Close();

    }

    public void WriteFile()

    {

        FileStream F = new FileStream("c:\\file.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite);

         for (int i = 1; i <= 20; i++)

         {

            F.WriteByte((byte)i);

         }

         F.Close();

    }

    public void ReadFile()

    {

        FileStream F = new FileStream("c:\\file.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite);

        F.Position = 0;

         for (int i = 0; i <= 20; i++)

         {

            Console.Write(F.ReadByte() + " ");

         }

         F.Close();

    }

}

We use the same FileStream class to open the file. Since we used the ReadWrite FileAccess mode, we can read and write to the file even though we're only going to read from the file in this method.

ReadFile isn't much different than the WriteFile method. We use the same FileStream class except this time we use the for loop to read each line from the file. Notice that we first position the file stream at the beginning of the file using the Position property. Remember that all values begin at zero with C#, so Position 0 is the beginning of the file. Within the for loop, we read each byte and print it to the screen. An integer is a byte long, so the read input is one integer during each loop.

With error and file handling, you have your final concepts that complete C# programming. It takes months of practice and working with the language to finally understand how all .NET libraries function. With some time and practice, you can make fully functional applications for either desktops or the web.