Team LiB
Previous Section Next Section

Throwing Errors

I know that up to now my examples have been seriously lacking in error checking. That's because I don't write buggy code.

Seriously, I have avoided major error checking because it sometimes gets in the way of what I really want to show you. However, error checking is very important to any professional-level program.

There is something called exception handling in .NET, where you can wrap some code inside a Try-Catch-Finally block. Any errors that happen because of this code—no matter how serious—will cause the code to end up in the Catch portion of the Try-Catch-Finally block. This is good because it is here that you can detect what happened and do something nice about it.

The Generic Error

Most of the examples I have seen concerning throwing and catching errors have been really simplistic. They go something like this.

C#

    try
    {
      StreamReader file = new StreamReader("MyFile.txt");
      string s = file.ReadLine();
      int x = int.Parse(s);
      s = file.ReadLine();
      int y = int.Parse(s);

      int z = x/y;
      MessageBox.Show("The value of X/Y is " + x.ToString());

    }
    catch
    {
      MessageBox.Show("Some error happened");
      this.Close();
    }

VB

    Try
      Dim file As StreamReader = New StreamReader("MyFile.txt")
      Dim s As String = file.ReadLine()
      Dim x As Int32 = Int32.Parse(s)
      s = file.ReadLine()
      Dim y As Int32 = Int32.Parse(s)

      Dim z As Int32 = CInt(x / y)
      MessageBox.Show("The value of X/Y is " + x.ToString())

    Catch
      MessageBox.Show("Some error happened")
      Me.Close()
    End Try

This is catching errors at its simplest. No error trapping at all would have given you a better understanding of what happened.

Essentially, three things could have gone wrong here:

  • The file might not exist. This would make the StreamReader instantiation fail.

  • The file might exist but be empty. This would make the read return a null string, which causes an error in the parse line.

  • The value of "y" might be zero, which throws an error during the division.

How would you know what happened and how would you present the error to the user? Well, you could catch an error. Change the Catch block to read as follows.

C#

    catch(Exception ex)
    {
      MessageBox.Show(ex.Message);
      this.Close();
    }

VB

    Catch ex As Exception
      MessageBox.Show(ex.Message)
      Me.Close()
    End Try

You are now catching the general exception. This is better, but it can still cause some confusion. For instance, if the error caught was in the parsing of the string to an integer, you would get the error shown in Figure 7-13.


Figure 7-13: A confusing error

Do you know which error this is? I don't. It could have come from either of the two parsing operations.

This next code shows a much more detailed Catch block. It includes specific error catching and detailed error messages.

C#

    catch(IOException ex)
    {
      MessageBox.Show("File I/O Exception: " + ex.Message);
    }
    catch(ArgumentNullException ex)
    {
      MessageBox.Show("Parse Error: Null string\n" + ex.Message);
    }
    catch(FormatException ex)
    {
      MessageBox.Show("Parse Error: string was not numeric\n" + ex.Message);
    }
    catch(DivideByZeroException ex)
    {
      MessageBox.Show("Math error: 'y' was zero\n" + ex.Message);
    }
    catch(Exception ex)
    {
      MessageBox.Show(ex.Message);
    }

VB

    Catch ex As IOException
      MessageBox.Show("File I/O Exception: " + ex.Message)
    Catch ex As ArgumentNullException
      MessageBox.Show("Parse Error: Null string\n" + ex.Message)
    Catch ex As FormatException
      MessageBox.Show("Parse Error: string was not numeric\n" + ex.Message)
    Catch ex As DivideByZeroException
      MessageBox.Show("Math error: 'y' was zero\n" + ex.Message)
    Catch ex As Exception
      MessageBox.Show(ex.Message)
    End Try

As you can see, I catch almost every error that can happen within the Try block. The only thing I can't tell you is which parse line threw the ArgumentNullException or FormatException. This is the preferred method of catching errors. First try to catch the most specific errors and then catch the most general one. This is kind of like a switch-case block in C# (Select-Case in VB), where the last case statement is the default. You have something to fall back on if none of the others is run.

So, is this better than the simple Exception catch? Maybe a little, as it gives more detailed messages, but this code is awfully long and busy. Can you see doing this for every method? Also, what happens in the case of an error (besides showing a message) if there is a failure?

Catching Errors Before They Happen

The better way to catch errors like this is before they happen. I can easily adjust this example, where I am opening up a file, reading the contents, parsing some values, and doing a division, so it is more readable and also more robust. Here is what this example should look like.

C#

    try
    {
      int x, y, z;
      StreamReader file = new StreamReader("MyFile.txt");
      string s = file.ReadLine();
      if(s == string.Empty || s == null)
        throw new ApplicationException("First line was empty");

      x = int.Parse(s);
      s = file.ReadLine();
      if(s == string.Empty || s == null)
        throw new ApplicationException("Second line was empty");

      y = int.Parse(s);
      if(y != 0)
      {
        z = x/y;
        MessageBox.Show("The value of X/Y is " + z.ToString());
      }
      else
        MessageBox.Show("Unable to divide numbers");
    }
    catch(ApplicationException ex)
    {
      MessageBox.Show(ex.Message);
      //Put a trace message here
    }
    catch(IOException ex)
    {
      MessageBox.Show("File I/O Exception: " + ex.Message);
      //Put a trace message here
    }
    catch(Exception ex)
    {
      MessageBox.Show(ex.Message);
      //Put a trace message here
    }

VB

    Try
      Dim file As StreamReader = New StreamReader("MyFile.txt")
      Dim s As String = file.ReadLine()
      If s = String.Empty Or s Is Nothing Then
        Throw New ApplicationException("First line was empty")
      End If
      Dim x As Int32 = Int32.Parse(s)
      s = file.ReadLine()
      If s = String.Empty Or s Is Nothing Then
        Throw New ApplicationException("second line was empty")
      End If
      Dim y As Int32 = Int32.Parse(s)
      If y <> 0 Then
      Dim z As Int32 = CInt(x / y)
        MessageBox.Show("The value of X/Y is " + x.ToString())
      Else
        MessageBox.Show("Unable to divide numbers")
      End If
    Catch ex As IOException
      MessageBox.Show("File I/O Exception: " + ex.Message)
      'Put a trace message here
    Catch ex As ApplicationException
      MessageBox.Show(ex.Message)
      'Put a trace message here
    Catch ex As Exception
      MessageBox.Show(ex.Message)
      'Put a trace message here
    End Try

This code has fewer catches, and it can also catch which line of parse code was invalid. I put some simple error checking in the Try block before I did any major operations. The only thing I can't check for is some unknown I/O error when opening the file. I may not have permission, the file may not exist, and so on. For this I still need to catch the IOException.

By the way, there should be no excuse for a divide by zero exception to appear. Anytime you think that you may have a divisor of zero, check before you do the math. Many programs have blown up due to a divide by zero error that was not caught.

Note 

Notice that I threw an application error with my own message. This allowed me to drill down deeper and get the specific parsing error. You can also create your own errors. See the online help.

The Finally Block

There is more to the Try-Catch block. There is a Finally block that you can also add. The Finally block is interesting in that is it guaranteed to run no matter what. You can take advantage of this in several ways, as I demonstrate in this section.

The Finally block comes last after the Try-Catch block. Its main use is to take care of some housekeeping that may have been skipped over during the Try block. As you have probably figured out, the Try-Catch block jumps over any subsequent code in the Try block if there is an error. There is no way to go back to this code.

Before I go on, I must tell you about one gotcha related to Try blocks. Any object that you want to access outside of the Try block must be defined outside of the Try block. Here is what I mean.

C#

    private void foo()
    {
      try
      {
        StreamReader file = new StreamReader("MyFile.txt");
        file.Close();
      }
      catch(Exception ex)
      {
        MessageBox.Show(ex.Message);
      }

      file.Close();
    }

VB

  Private Sub foo()
    Try
      Dim file As StreamReader = New StreamReader("MyFile.txt")
      file.Close()
    Catch ex As Exception
      MessageBox.Show(ex.Message)
    End Try

    File.Close()
  End Sub

If the StreamReader initialization fails, the first attempt at closing the file will not get run. If you try to compile this code, however, you will get an error stating that the compiler does not recognize the variable "file". This variable was defined inside the Try block and that is the extent of its scope. To have the compiler recognize this "file" variable, I need to define it before the Try block as follows.

C#

    private void foo()
    {
      StreamReader file;
      try
      {
        file = new StreamReader("MyFile.txt");
        file.Close();
      }
      catch(Exception ex)
      {
        MessageBox.Show(ex.Message);
      }

      if(file != null)
        file.Close();
    }

VB

  Private Sub foo()
    Dim file As StreamReader
    Try
      File = New StreamReader("MyFile.txt")
      file.Close()
    Catch ex As Exception
      MessageBox.Show(ex.Message)
    End Try

    If Not file Is Nothing Then
      file.Close()
    End If
  End Sub

Now the compiler will be happy. By the way, I made sure that the "file" object was null before I closed it outside of the Try block. I did not do this while inside the Try block. Anyone know why?[2]

Now when you wrap the last few lines in a Finally block, your program should also work. Here is the code.

C#

    private void foo()
    {
      StreamReader file;
      try
      {
        file = new StreamReader("MyFile.txt");
        file.Close();
      }
      catch(Exception ex)
      {
        MessageBox.Show(ex.Message);
      }
      finally
      {
        if(file != null)
          file.Close();
      }
    }

VB

  Private Sub foo()
    Dim file As StreamReader

    Try
      File = New StreamReader("MyFile.txt")
      file.Close()
    Catch ex As Exception
      MessageBox.Show(ex.Message)
    Finally
      If Not file Is Nothing Then
        file.Close()
      End If
    End Try
  End Sub

You may be wondering what the point of the Finally block is. In this case, the point is to get rid of the first instance of closing the file. You see the Finally block is guaranteed to run no matter what. So whether the Try block was successful or not, the file object would still get released before the method ended. This next bit of code shows the true power of this concept.

C#

    private void FooBar()
    {
      StreamReader file = null;
      SolidBrush B      = new SolidBrush(Color.Azure);
      Pen P             = new Pen(B, 3);
      Font F            = new Font("Arial", 12);
      Graphics G        = null;

      try
      {
        G = Graphics.FromHwnd(this.Handle);
        file = new StreamReader("MyFile.txt");
        string s = file.ReadLine();
        G.DrawString(s, F, B, 10, 20);
        // Do some funky stuff with the pen here
        // Also do some extensive code
        //...
        //...
        if(s == "The End")
          return;
        s = file.ReadLine();
        // Do some more stuff with the pen here
        // Also do some more extensive code
        //...
        //...
        G.DrawString(s, F, B, 30, 20);

      }
      catch(Exception ex)
      {
        MessageBox.Show(ex.Message);
      }
      finally
      {
        if(G != null)
          G.Dispose();
        if(F != null)
          F.Dispose();
        if(P != null)
          P.Dispose();
        if(B != null)
          B.Dispose();
        if(file != null)
          file.Close();
      }
    }

VB

  Private Sub FooBar()
    Dim file As StreamReader = Nothing
    Dim B As SolidBrush = New SolidBrush(Color.Azure)
    Dim P As Pen = New Pen(B, 3)
    Dim F As Font = New Font("Arial", 12)
    Dim G As Graphics = Nothing

  Try
    G = Graphics.FromHwnd(Me.Handle)
    file = New StreamReader("MyFile.txt")
    Dim s As String = file.ReadLine()
    G.DrawString(s, F, B, 10, 20)
    ' Do some funky stuff with the pen here
    ' Also do some extensive code
    '...
    '...
      If s = "The End" Then
        Return
      End If

      s = file.ReadLine()
      ' Do some more stuff with the pen here
      ' Also do some more extensive code
      '...
      '...
      G.DrawString(s, F, B, 30, 20)

    Catch ex As Exception
      MessageBox.Show(ex.Message)
    Finally
      If Not G Is Nothing Then
        G.Dispose()
      End If
      If Not F Is Nothing Then
        F.Dispose()
      End If
      If Not P Is Nothing Then
        P.Dispose()
      End If
      If Not B Is Nothing Then
        B.Dispose()
      End If
      If Not file Is Nothing Then
        file.Close()
      End If
      end try
  End Sub

In this method I instantiate quite a few objects that take up memory. Because it is always nice to clean up after myself, I dispose of these objects in the Finally block.

Note 

The common language runtime's (CLR's) garbage collector (GC) takes care of cleaning up managed resources, but it does this in its own sweet time. If you forget to dispose of managed resources, the GC will eventually get around to it. However, if you open a serial port or some raw GDI object such as a brush, you will need to get rid of it once you are done. The GC does not know about this stuff and will happily let you leak memory until your machine screams for mercy and your customers demand their money back.

Now, what is interesting is that there are some return statements in the Try block that bail out of the code depending on some value. This kind of thing happens all the time in software.

I use the Finally block here to make sure that these objects are disposed of before I leave this method. Even though I told the compiler I wanted to return halfway through the code, the Finally block still gets run.

Quite often I will use a Try-Finally block just for this purpose in some complicated or long methods. It is easy to instantiate objects, work with them for a week, and then forget to dispose of them at the end of your code. If you create the Finally block and dispose of every object you create at the same time you type in the code to create them, you will not make that mistake.

Note 

The code for all this Try-Catch stuff is included in the code for this book, which you can obtain from the Downloads section of the Apress Web site (http://www.apress.com). The project is called "PlayCatch."

[2]Because inside the Try block I know that this object is not null. There's no need to check.


Team LiB
Previous Section Next Section