How to Have a Method Continue Even After a Catch Method

  • Download source - 15.75 KB

Introduction

Error handling is an important part of any software developers' job. The .NET Framework offers some very powerful tools to handle errors in a relatively easy manner.
One of these tools is the Try... Catch... Finally... statement. Yet in the few months that I have been programming, I have seen quite some (wrong) uses of the Try... Catch... Finally... not only by beginning progammers, but also by so-called seniors and gurus!
There are many books and articles discussing correct error handling in .NET, but many fail to give a simple yet complete overview of the so-important Try... Catch... Finally... block.
With this article, I hope to inform any software developer about some proper and detailed uses of the Try... Catch... Finally... block.

Using the Example Project

For this article, I have made a small example application dealing with various uses of the Try... Catch... Finally... statement. The example is easy to use and debug.
The example consists of a single form with some buttons on it. Each button demonstrates a specific use of the Try... Catch... Finally... block and should be debugged using breakpoints to fully understand. Note that the code itself does not actually do anything. It simply throws exceptions and handles them.

The project was made using Visual Studio 2010 Express Version using .NET Framework 4.0. However, all of the code could easily be used in any .NET 3.5 application as well.

UsingTryCatchFinally.jpg

Some Basics about Exceptions

No matter how well you test an application, eventually an error will always occur. Perhaps a connection to a database fails, or you are trying to open a file which does not exists on the users' computer or to which the user has no access. If such an error occurs, the .NET Framework gathers information about what just went wrong and stores this in an Exception object which is then thrown up (or down?) the stack. The .NET Framework knows numerous types of Exceptions, such as the FileNotFoundException, IndexOutOfRangeException and the SqlException. Some of these Exception classes provide specific information about what just happened. For example, the SqlException contains information about the severity of the SqlError.

No matter what kind of Exception is thrown, they all inherit from the System.Exception class. When an Exception is thrown by the .NET Framework, it automatically looks for the first Catch block (contained in a Try... Catch... block) in the stack. If the stack does not contain any Catches, an Unhandled Exception occurred. This will give your end-user a prompt saying that an Unhandled Exception occurred and that they can continue and ignore the error or quit the application. What happens next is a mystery. In the best case, your application is closed either way. In the worst case... Let's just not think about that. :)
As a developer, you generally do not want users to see such a message. Therefore, it is important to always implement error handling in your code by using Try... Catch... blocks.

Starting the Sample Project

When you open the example project, you will see a form with numerous buttons.
I know the big red button looks tempting, but let's not press it right away. Instead, first build the project. Now go to your debug folder (usually located in your solution folder) and run the executable. Now you may press the big red button. That does not look very good, does it?
Now press any other of the buttons (except Close) and notice the difference. Go back to the project file again. Before running the application, I want you to press the shortcut keys Control + Alt + E in Visual Studio. This will display the Exception Dialog. Make sure both checkboxes behind 'Common Language Runtime Exceptions' are checked. This will make sure that you go into debug mode whenever an Exception is thrown in your code. It is recommended that you fully step through the code of any button by using Breakpoints and F8 to follow the paths that Exceptions make when dealing with Try... Catch... Finally blocks.

A Simple Try... Catch...

Open the project and press the first button on the form, simple Try... Catch.... You will immediately be sent to the source code. It is not a very exciting piece of code, but this is where it all starts.

          Private          Sub          btnSimpleTryCatch_Click(ByVal          sender          As          System.Object, _          ByVal          e          As          System.EventArgs)          Handles          btnSimpleTryCatch.Click          Try                                 Throw          New          System.Exception("          An unexpected error occurred")                       Catch          ex          As          Exception                          MessageBox.Show(ex.Message,          Me.Text, _ 		MessageBoxButtons.OK, MessageBoxIcon.Error)          End          Try          End          Sub        

Notice what happens. I am trying (Try...) to run some code, but for some reason, an Exception is thrown. Instead of crashing my application, the Exception looks for the first Catch it can find. Fortunately, a Catch happens to be nearby and the Exception can be handled. Be aware of the fact that any code between the Throw and the Catch is NOT executed. The Exception jumps over this code and goes straight to the Catch block. Also notice that I Catch ex as Exception. Remember that every type of Exception inherits from the Exception object? That means that this little bit of code is able to catch ANY Exception! In this case, I simply let the user know that something went wrong by using a MessageBox and showing the Message property of the Exception object. Now, whatever code is between the Try... Catch... and whatever might go wrong is automatically handled by showing a prompt to the user. Easy, no?

A Simple Try... Catch... Finally...

Let's move on to the second button on the form. Click it and notice that your cursor turns into a waiting cursor! Unfortunately after a few seconds, all goes wrong...

          Private          Sub          btnSimpleTryCatchFinally_Click(ByVal          sender          As          System.Object, _          ByVal          e          As          System.EventArgs)          Handles          btnSimpleTryCatchFinally.Click                   Me.Cursor = Cursors.WaitCursor          Try                                    Threading.Thread.Sleep(5000)                       Throw          New          System.Exception("          An unexpected error occurred.")                       Catch          ex          As          Exception                          MessageBox.Show(ex.Message,          Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error)          Finally                    Me.Cursor = Cursors.Default          End          Try          End          Sub        

This piece of code looks very much like that of the previous button, with the exception that this is a long process and we turn the cursor into a wait cursor. I have often waited a long time for applications that appeared to be loading, but had actually crashed which caused my cursor to always stay in waiting mode! So how do we make sure the cursor in OUR application always turns back to default? Easy, we use a Finally... block. The Finally... statement in a Try... Catch... Finally... block is ALWAYS executed. So if everything would go as it should, my code would be executed, it would skip the Catch block, go into my Finally block and put my cursor back to normal. If an Exception occurs (like here), the code jumps into the Catch block, handles the error and then goes into my Finally block, making sure that my cursor is turned back to normal. Notice how I initially change my cursor outside of the Try... Catch... Finally... block. This makes sure that when I go into my Finally, my cursor is actually a wait cursor. In this example, it would not matter if the first line of code is inside or outside the Try... Catch... Finally... block. But it will matter later on as you will see. I should also mention that the Try... Finally... block can also be used without the Catch. We will see an example of that later on.

Multiple Catches

In some cases, you might want to handle certain Exceptions differently than others. The following example provides an explanation of multiple Catches in a single Try... Catch... block. For the example, let's just say we are trying to open a file on the users' computer. Anything could happen. Maybe the directory of the file you are trying to open does not exist, maybe the file does not exist, maybe the user has no rights to open the file or maybe something completely unexpected occurs. In this case, you can use multiple catches, as the following code snippet shows:

          Private          Sub          btnMultipleCatches_Click(ByVal          sender          As          System.Object, _          ByVal          e          As          System.EventArgs)          Handles          btnMultipleCatches.Click          Try                                              Dim          rand          As          New          Random          If          rand.Next(2) =          1          Then          Throw          New          IO.FileNotFoundException_ 		("          The file you are trying to open was not found.")          Else          Throw          New          System.Exception("          An unknown error occurred.")          End          If                    Catch          dirEx          As          IO.DirectoryNotFoundException                          MessageBox.Show(dirEx.Message,          "          Directory not found", _ 		MessageBoxButtons.OK, MessageBoxIcon.Error)          Catch          fileEx          As          IO.FileNotFoundException                          MessageBox.Show(fileEx.Message,          "          File not found", _ 		MessageBoxButtons.OK, MessageBoxIcon.Error)          Catch          ioEx          As          IO.IOException                          MessageBox.Show(ioEx.Message,          "          IO exception", _ 		MessageBoxButtons.OK, MessageBoxIcon.Error)          Catch          ex          As          Exception                          MessageBox.Show(ex.Message,          "          Unknown exception", _ 		MessageBoxButtons.OK, MessageBoxIcon.Error)          End          Try          End          Sub        

Note how an Exception is generated using the Random class. 50% of the time, we will get a FileNotFoundException and 50% of the time, we will get a non-specific Exception.
At this point, you might want to uncheck the checkbox you checked at the beginning of this article by using Control + Alt + E and press the 'Multiple catches' button a couple of times to see how it can show two different MessageBoxes (either the "File not found" or the "Unknown exception" MessageBoxes).

If the Exception is thrown, it will look at the first Catch block that it can use. In this case, for the FileNotFoundException this is the Catch fileEx As IO.FileNotFoundException. Notice that the FileNotFoundException skips the Catch dirEx As IO.DirectoryNotFoundException block, because it is no DirectoryNotFoundException. Also, if the fileEx has been handled in the fileEx block, it will not step into the ioEx or the ex block anymore.

Both the FileNotFoundException and the DirectoryNotFoundException inherit from the IOException. This means that if I had put the Catch ioEx As IO.IOException block above the other two Catch blocks, the FileNotFoundException and the DirectoryNotFoundException would never jump in their respective Catch blocks!
When handling Exceptions of multiple types, you must first specify the most specific Exception block. The general Catch ex as Exception should always be on the bottom of the hierarchy because every Exception can jump into this block, preventing them from jumping into more specific Catch blocks after that.

A Finally... block could still be added at the bottom of the Try... Catch... block.
If you have unchecked the 'Common Language Runtime Exception' checkbox by using Control + Alt + E, do not forget to turn it on again.

Exceptions from Deeper Methods

A common misconception about Try... Catch... is that every block of code should have one. This is not true in many (if not most) cases. To illustrate this, I have made a class named "ManyMethods" which exposes three Public members which step into a couple of methods. None (with the exception of one) of these methods contain a Try... Catch... block. Yet, the last method where all of these methods will end eventually always throws an Exception. If none of the methods has a Try... Catch... block, then where will this Exception be handled? Easy, in the first block of code on the stack that does have a Try... Catch... block. In this case, the event handler of the button.

          Private          Sub          btnExceptionsFromDeeperMethods_Click(ByVal          sender          As          System.Object, _          ByVal          e          As          System.EventArgs)          Handles          btnExceptionsFromDeeperMethods.Click          Try          Dim          mm          As          New          ManyMethods                  mm.GoIntoDeeperMethods()          Catch          ex          As          Exception         MessageBox.Show(ex.Message & Environment.NewLine & _          "          The method which threw me was: "          & ex.TargetSite.Name, _          Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error)          End          Try          End          Sub          Public          Sub          GoIntoDeeperMethods()     B()               MessageBox.Show("          You will never see me!",          "          Some deeper method", _     MessageBoxButtons.OK, MessageBoxIcon.Information)     AA()      AB()           End          Sub                           Private          Sub          ExceptionMethod()                    Throw          New          System.Exception("          I am coming from way down here!")                     End          Sub        

So even though none of the methods that this piece of code jumps into has a Try... Catch... block, the Exception will simply come back here and let the user know something went wrong.
Also note that I use the 'TargetSite' property of the Exception object. This property contains the method which threw the Exception (see the System.Reflection.MethodBase http://msdn.microsoft.com/en-us/library/system.reflection.methodbase.aspx namespace for more information). I am using this property to prove that the Exception really was thrown a few methods in the calling method, but that if an Exception occurs, it can simply be handled in the first Try... Catch... block, no matter where in the stack it is.

Also check the other methods in the ManyMethods class. They are all called by the methods the code runs through. Yet, because an Exception occurs, the calling code will never execute.

Finally in Deeper Methods

This example is almost the same as the previous one, with one difference. In this example, I put a method between my button Event Handler and the method that eventually calls the ExceptionMethod. This extra method contains a Try... Finally... block. So click the button and see what happens.

          Private          Sub          btnFinallyInDeeperMethods_Click(ByVal          sender          As          System.Object, _          ByVal          e          As          System.EventArgs)          Handles          btnFinallyInDeeperMethods.Click          Try          Dim          mm          As          New          ManyMethods                          mm.GoIntoDeeperMethodsWithFinally()          Catch          ex          As          Exception             MessageBox.Show(ex.Message & Environment.NewLine & _          "          The method which threw me was: "          & ex.TargetSite.Name, _          Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error)          End          Try          End          Sub          Public          Sub          GoIntoDeeperMethodsWithFinally()          Try                       GoIntoDeeperMethods()          Finally                       MessageBox.Show("          You will always see me!", _          "          Finally", MessageBoxButtons.OK, MessageBoxIcon.Information)          End          Try          End          Sub        

As you can see, when you press the button and step through the code, the Finally... statement in this block of code will be executed, even though the Exception is not thrown or handled here. Any Finally... block between the throwing of an Exception and the handling of an Exception will always be executed. This is especially useful when you have to Dispose of objects or make changes to the user interface (such as transforming the cursor in the first Finally... example).

Handling an Exception Multiple Times

When an Exception is thrown from a Class that is called from a Form, it is possible that you want to do some Exception Handling in the throwing Class before you handle the Exception in your Form to let users know something went wrong.

A practical example would be a Class that stores data to your database using a Transaction object. When an error occurs you want to rollback your transaction before you throw the Exception up to the calling Form.

A Finally... block would not suffice, because you would not know if the Transaction needs to be rolled back or committed. Handling the Exception in the Class itself is necessary, but an Exception can only be handled once and it would not be thrown to your calling Form.
There are basically two options you can consider. You either rethrow the Exception after you have handled it in your Class or you throw a new Exception, possibly passing the original Exception as an InnerException to the new Exception. The following code snippet shows the last option:

          Private          Sub          btnHandlingAnExceptionMultipleTimes_Click_ 	(ByVal          sender          As          System.Object,          ByVal          e          As          System.EventArgs) _          Handles          btnHandlingAnExceptionMultipleTimes.Click          Try          Dim          mm          As          New          ManyMethods                          mm.GoIntoDeeperMethodsWithCatch()          Catch          ex          As          Exception             MessageBox.Show(ex.Message & Environment.NewLine & _          "          The method which threw me was: "          & _ 			 ex.TargetSite.Name & Environment.NewLine & _          "          The method which initially threw me was: _ 			 "          & ex.InnerException.TargetSite.Name, _          Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error)          End          Try          End          Sub          Public          Sub          GoIntoDeeperMethodsWithCatch()          Try                       GoIntoDeeperMethods()          Catch          ex          As          Exception             MessageBox.Show("          Do some specific error handling here...", _          "          Some deeper method", MessageBoxButtons.OK, MessageBoxIcon.Information)                       Dim          newEx          As          New          System.Exception("          I am rethrown!", ex)          Throw          newEx          End          Try          End          Sub        

The button Event Handler calls a method, which in turn calls other methods. Note that the second method here also has a Try... Catch... block. Eventually, you will get an Exception which will be sent to the first Catch block it finds on the stack. In this case, this will be the Catch in the GoIntoDeeperMethodsWithCatch. But further down the stack, there is another Catch block that wants to notify the user that something went wrong.

So the first Catch block handles the Exception and then throws a new Exception with the original Exception as its InnerException. The InnerException property of the Exception object can contain another Exception. So what happens here is that the GoIntoDeeperMethodsWithCatch method handles the original Exception and then throws a new Exception to the Form. The Form then has information about the new Exception and the original Exception (which is contained in the InnerException property of the new Exception). Be careful when using the InnerException property of the Exception object. If no InnerException is provided, the above code will cause a NullReferenceException. An alternative to the above code is to simply Catch ex as Exception in the GoIntoDeeperMethodsWithCatch method and then Throw ex. In this case, the original Exception is thrown to the Form or Class above. Because in this case, your Exception will not have an InnerException you would have to edit the Exception handling in the button Event Handler too. Basically you would only create a new Exception if you want to add some information to the original Exception. For example, that the first Exception has already been handled.

Nested Try... Catch... Finally...

Sometimes, you want to do multiple error handlings in one block of code. In this case, it is possible to use nested Try... Catch... Finally... blocks.

          Private          Sub          btnNestedTryCatch_Click(ByVal          sender          As          System.Object, _          ByVal          e          As          System.EventArgs)          Handles          btnNestedTryCatch.Click          Try                    Try                    Dim          table          As          New          DataTable          Try                                         Throw          New          System.Exception("          I come from the third nested try.")                               Finally                               table.Dispose()          End          Try          Catch          ex          As          Exception                 MessageBox.Show(ex.Message & Environment.NewLine & _          "                      I was handled in the second Try... Catch... block.", _          Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error)          End          Try          Catch          ex          As          Exception             MessageBox.Show(ex.Message & Environment.NewLine & _          "                      I was handled in the first Try... Catch... block.", _          Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error)          End          Try          End          Sub        

In this example, there are three nested Try... blocks. The third one has no Catch, but a Finally only. This is no problem though, because if an Exception occurs here, it will simply be thrown to the second Try... block which does have a Catch that handles the Exception.
If an Exception occurs in the second or third nested Try... block, the Exception will never reach the first Try... block (unless it is rethrown). If the Exception occurs in the first block, it will never reach the second and third Try... blocks.

The Finally... will only be executed if your code makes it to the third Try... block. Of course you can also add Finally... blocks in the first and second Try... blocks and you can also add multiple Catches (for SqlException, IOException, etc.).

Also note that I have two variables named ex. This means that the exvariable only exists within the Catch block. Also, I declare the DataTable in the second nested Try... block which means it does not exist in the first Try... block, making it impossible to Dispose it in a Finally... block in the first Try... statement.

Using... End Using

If you are using an object that implements the IDisposable interface, you would generally do well if you actually disposed of it after you are done using it. The above examples illustrate that this can be done by using a Try... Finally... block. In the Finally... block, you can call the Dispose method of the object you are using. There is a shorter notation for these situations though.

          Private          Sub          btnUsingEndUsing_Click(ByVal          sender          As          System.Object, _          ByVal          e          As          System.EventArgs)          Handles          btnUsingEndUsing.Click          Dim          table1          As          New          DataTable          Try                    Finally          table1.Dispose()          End          Try                                               Using          table2          As          New          DataTable                       End          Using          MessageBox.Show("          This button does not actually do something."          _ 	& Environment.NewLine &          "          Check the source code :)", _          Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error)          End          Sub        

The Using... End Using block Disposes any object for you, no matter what happens. This example only shows the shortened syntax for the Try... Finally... Dispose pattern. Nested Using... End Using blocks are also allowed, as we will see in the final example.

To Sum It Up...

This final example combines some of the above examples, using Try... Catch... Finally and Using blocks. This example does create an actual Exception, so it will not be artificially thrown from the code. Let's look at this practical example.

          Private          Sub          btnToSumItUp_Click(ByVal          sender          As          System.Object, _          ByVal          e          As          System.EventArgs)          Handles          btnToSumItUp.Click          Me.Cursor = Cursors.WaitCursor          Try          DoSomeSqlStuff()                                                 Catch          sqlEx          As          SqlClient.SqlException             MessageBox.Show(sqlEx.Message,          "          SQL Exception", _ 		MessageBoxButtons.OK, MessageBoxIcon.Error)          Catch          ex          As          Exception             MessageBox.Show(ex.Message,          "          Unknown exception", _ 		MessageBoxButtons.OK, MessageBoxIcon.Error)          Finally                    Me.Cursor = Cursors.Default          End          Try          End          Sub          Private          Sub          DoSomeSqlStuff()          Using          connection          As          New          SqlClient.SqlConnection_ 	("          Data Source=SomeServer;Initial Catalog=SomeDB;Integrated Security=True")          Using          cmd          As          New          SqlClient.SqlCommand_ 		("          SELECT * FROM SomeTable", connection)                 connection.Open()          Try          Using          reader          As          SqlClient.SqlDataReader = cmd.ExecuteReader          While          reader.Read                                       End          While          End          Using                    Finally                               connection.Close()          End          Try          End          Using                    End          Using                    End          Sub        

In this example, my button Event Handler tries to connect to a database. Before it does that, the cursor is transformed into a wait cursor.

In the called method, I declare a Connection object and pass a valid connection string to it. The Connection object implements IDisposable, so I can use the Using statement to declare it. Note that the connection variable only exists within the Using... End Using block. Anything I want to do with the connection should therefore be done in the Using... End Using block.

Next, I declare a Command object in the same way I declared the Connection object. I pass the connection to the Command objects constructor.
Next, I try to open the connection. This will of course never work with the provided connectionstring. The syntax is all right, but the Data Source and Initial Catalog (your SQLServer instance) simply do not exist! So, opening the connection will fail and throw an Exception.

Note that the code does not reach the Try... Finally... block and the connection will not be closed. This is all right, because we could not open it in the first place. Only after the connection is opened, it would be important to close it and thus get into the Try... Finally... block. The code will now first get to the End Using statements and dispose of your Command object and Connection object (in that order). The Exception is then handled one method up, in the button Event Handler. Here, it can be caught as an SqlException (which is the case for this Exception) or as any other Exception. After the Exception is handled, the code will go into the Finally... block and change my cursor back to default.

An alternative approach to DoSomeSqlStuff would be the following code, which does not make use of the Using... End Using statement for the Connection and Command objects.

          Private          Sub          AlternativeDoSomeSqlStuff()          Dim          connection          As          New          SqlClient.SqlConnection_    ("          Data Source=SomeServer;Initial Catalog=SomeDB;Integrated Security=True")          Dim          cmd          As          New          SqlClient.SqlCommand("          SELECT * FROM SomeTable", connection)          Try          connection.Open()          Using          reader          As          SqlClient.SqlDataReader = cmd.ExecuteReader          While          reader.Read                              End          While          End          Using                    Finally          connection.Close()            connection.Dispose()            cmd.Dispose()          End          Try          End          Sub        

Note here that if the application fails to create a new Connection object (for example because the connection string is not syntaxically correct), the code will never jump into the Try... Finally... block, but go straight to the error handling in the button Event Handler. If the creation of the Command object fails, you would also not go into the Try... Finally... block and your Connection object would not be disposed. The chances of that happening are really very small though, so in this case it would be smart to neglect it, or you would have to add another Try... Finally... block, making your code long and difficult to read (although the Using... End Using block would be prepared for such a rare Exception). Also note that if the connection fails to open, it will still be closed in the Finally... block. Though it is not really necessary, it will not throw an Exception and it makes your code more compact and readable to not add another Try... Finally... block here. If you want, you could check the State property of your connection object before closing it though.

Points of Interest

  • The Try... Catch... Finally... block should be used to handle Exceptions ONLY!!! Too often have I seen actual business logic in Try... Catch... blocks. This will make your software depend on Exceptions. And you do not want that!
  • Always try to handle Exceptions at user interface level. When something goes wrong, you want to notify the user about this. Do not try to do this in underlying classes. This will make your classes less usable in other objects. Think for example that you have a Class that handles e-mailing in your application. The first form to implement e-mailing is the company products form. When the e-mailing fails and throws an Exception, you do not want your class to prompt the user that the product information could not be e-mailed. Because if you start using this Class in other forms too, let's say a sales order form, it would look kind of weird if you could not e-mail product information (you are mailing sales order information!).
  • You can handle Exceptions in underlying classes, but do not communicate them to the user from there. If you want to handle an Exception in a Class other than your user form, rethrow the Exception up the stack instead (or pass it as an InnerException to a new Exception object). Think where, when and how you want to throw and handle Exceptions.
  • Experiment with Exceptions. You can create your own Exception classes by inheriting from the System.Exception class and adding properties and/or functionality.

History

  • 5th February, 2011: Initial post

lopezfeby2002.blogspot.com

Source: https://www.codeproject.com/articles/154121/using-try-catch-finally

0 Response to "How to Have a Method Continue Even After a Catch Method"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel