Handling exceptions

You've seen several scenarios when errors have occurred. C# calls where an exception has been thrown. A good practice is to avoid writing code that will throw an exception whenever possible, but sometimes you can't. In those scenarios, you must catch the exception and handle it.

As you have seen, the default behavior of a console application is to display details about the exception in the output and then stop running the application.

The default behavior of a Windows desktop application is to display details about the exception in a dialog box and allow the user to choose to either continue or stop running the application. You can take control over how to handle exceptions using the try statement.

The try statement

Add a new console application project named Ch03_HandlingExceptions.

When you know that a statement can cause an error, you should wrap that statement in a try block. For example, parsing from a string to a number can cause an error. We do not have to do anything inside the catch block. When the following code executes, the error will get caught and will not be displayed, and the console application will continue running.

In the Main method, add the following statements:

    WriteLine("Before parsing"); 
    Write("What is your age? "); 
    string input = Console.ReadLine(); 
    try 
    { 
      int age = int.Parse(input); 
      WriteLine($"You are {age} years old."); 
    } 
    catch 
    { 
 
    } 
    WriteLine("After parsing"); 

Run the console application and enter a valid age, for example, 43:

Before parsing
What is your age? 43
You are 43 years old.
After parsing

Run the console application again and enter an invalid age, for example, kermit;

Before parsing
What is your age? kermit
After parsing

The exception was caught, but it might be useful to see the type of error that occurred.

Catching all exceptions

Modify the catch statement to look like this:

    catch(Exception ex)
    
{
  
      WriteLine($"{ex.GetType()} says {ex.Message}"); 
    } 

Run the console application and again enter an invalid age, for example, kermit:

Before parsing
What is your age? kermit
System.FormatException says Input string was not in a correct format.
After parsing

Catching specific exceptions

Now that we know which specific type of exception occurred, we can improve our code by catching just that type of exception and customizing the message that we display to the user.

Leave the existing catch block, but add the following code above it:

    catch (FormatException)

    {

      WriteLine("The age you entered is not a valid number format.");

    } 
    catch (Exception ex) 
    { 
      WriteLine($"{ex.GetType()} says {ex.Message}"); 
    } 

Run the program and again enter an invalid age, for example, kermit:

Before parsing
What is your age? kermit
The age you entered is not a valid number format.
After parsing

The reason we want to leave the more general catch below is because there might be other types of exceptions that can occur. For example, run the program and enter a number that is too big for an integer, for example, 9876543210:

Before parsing
What is your age? 9876543210
System.OverflowException says Value was either too large or too small  for an 
Int32.
After parsing

Let's add another catch for this new type of exception:

    catch(OverflowException)

    {

      WriteLine("Your age is a valid number format but it is either
      too big or small.");

    } 
    catch (FormatException) 
    { 
      WriteLine("The age you entered is not a valid number format."); 
    } 

Rerun the program one more time and enter a number that is too big:

Before parsing
What is your age? 9876543210
Your age is a valid number format but it is either too big or small.
After parsing

Note

The order in which you catch exceptions is important. The correct order is related to the inheritance hierarchy of the exception types. You will learn about inheritance in Chapter 6, Building Your Own Types with Object-Oriented Programming. However, don't worry too much about this---the compiler will give you build errors if you get exceptions in the wrong order anyway.

The finally statement

Sometimes, we might want to ensure that some code executes regardless of whether an exception occurs or not. To do this, we use a finally statement.

A common scenario where you would want to use finally is when working with files and databases. When you open a file or a database, you are using resources outside of .NET. These are called unmanaged resources and must be disposed of when you are done working with them. To guarantee that they are disposed of, we can call the Dispose method inside of a finally block.

Note

You will learn about files and databases in more detail in later chapters. For now, focus on the code that we write in the finally block.

Import the System.IO namespace at the top of the code file as follows:

    using System.IO; 

Type the following code to the end of the Main method:

Note

If you are using macOS then swap the commented statement that sets the path variable and replace my username with your user folder name.

    // string path = "/Users/markjprice/Code/Chapter03"; // macOS 
    string path = @"C:CodeChapter03"; // Windows 
 
    FileStream file = null; 
    StreamWriter writer = null; 
    try 
    { 
 
      if (Directory.Exists(path)) 
      { 
        file = File.OpenWrite(Path.Combine(path, "file.txt")); 
        writer = new StreamWriter(file); 
        writer.WriteLine("Hello, C#!"); 
      } 
      else 
      { 
        WriteLine($"{path} does not exist!"); 
      } 
    } 
    catch (Exception ex) 
    { 
      // if the path doesn't exist the exception will be caught 
      WriteLine($"{ex.GetType()} says {ex.Message}"); 
    } 
    finally 
    { 
      if (writer != null) 
      { 
        writer.Dispose(); 
        WriteLine("The writer's unmanaged resources have been
        disposed."); 
      } 
      if (file != null) 
      { 
        file.Dispose(); 
        WriteLine("The file's unmanaged resources have been
        disposed."); 
      } 
    } 

Run the console application and view the output:

The writer's unmanaged resources have been disposed.
The file's unmanaged resources have been disposed.

If you browse to the folder specified in the path, then you will see a file has been created named file.txt that contains the text: Hello, C#!

Simplifying disposal with the using statement

If you don't need to catch any exceptions, then you can simplify the code that needs to check for a non-null object and then call its Dispose method by using the using statement.

Note

Confusingly, there are two uses for the using statement: importing a namespace, and generating a finally statement that disposes of an object.

The compiler changes your code into a full try and finally statement, but without a catch. You can use nested try statements; so, if you do want to catch any exceptions, you can.

Add this code after the existing code. It will create a file named file2.txt:

    using (FileStream file2 = File.OpenWrite( 
      Path.Combine(path, "file2.txt"))) 
    { 
      using (StreamWriter writer2 = new StreamWriter(file2)) 
      { 
        try 
        { 
          writer2.WriteLine("Welcome, .NET Core!"); 
        } 
        catch (Exception ex) 
        { 
          WriteLine($"{ex.GetType()} says {ex.Message}"); 
        } 
      } // automatically calls Dispose if the object is not null 
    } // automatically calls Dispose if the object is not null 

Note

Many types, including FileStream and StreamWriter mentioned earlier, provide a Close method as well as a Dispose method. In the .NET Framework, you can use either because they do the same thing. In the .NET Core, Microsoft has simplified the API, so you must use Dispose.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.222.38