Exception Handling

An exception has come to stand for almost any type of error, but that's not how the word was originally intended. For example, bugs are errors in the logic of your code, but they're not exceptions. Similarly, the term error is often reserved for a problem in user inputthe user might have entered an out-of-bounds number, for example. However, you are supposed to handle such errors with validation code that checks input and other user actions without resorting to the special kind of code meant to handle exceptions.

Exceptions are not preventable in the same way as errors. They're the problems that occur because of runtime conditionsyou might be out of memory, for example, or there might not be a disk in a drive.

In C#, as in many other languages, you handle exceptions with try-catch statements. The try-catch statement is made up of a try block followed by one or more catch clauses, which are the actual handlers for different exceptions. The try-catch statement looks like this:

 
 try  try-block  catch (  exception-declaration-1  )  catch-block-1   .   .   .  catch (  exception-declaration-n  )  catch-block-n  

Here are the parts of this statement:

  • try-block Contains the sensitive code that can cause an exception.

  • exception-declaration-1 , exception-declaration-n An exception object declaration used to filter the exceptions this catch block will handle.

  • catch-block-1 , catch-block-n Contains exception handler code.

The exceptions you use in C# use objects based on the System.Exception class. C# itself bases several class in the System.Exception class, as you see here:

 
 System.Object   System.Exception    System.ApplicationException    System.IO.IsolatedStorage.IsolatedStorageException    System.Runtime.Remoting.MetadataServices.SUDSGeneratorException    System.Runtime.Remoting.MetadataServices.SUDSParserException    System.SystemException    System.Windows.Forms.AxHost.InvalidActiveXStateException 

Each of these classes in turn has many exception classes based on it; for example, the most common types of exceptions to use in your own programming are based on the SystemException class. Here are those exceptions (many of which have other exceptions based on them as well):

 
 System.Object   System.Exception    System.SystemException     System.AppDomainUnloadedException     System.ArgumentException     System.ArithmeticException     System.ArrayTypeMismatchException     System.BadImageFormatException     System.CannotUnloadAppDomainException     System.ComponentModel.Design.Serialization.CodeDomSerializerException     System.ComponentModel.LicenseException     System.ComponentModel.WarningException     System.Configuration.ConfigurationException     System.Configuration.Install.InstallException     System.ContextMarshalException     System.Data.DataException     System.Data.DBConcurrencyException     System.Data.Odbc.OdbcException     System.Data.OracleClient.OracleException     System.Data.SqlClient.SqlException     System.Data.SqlServerCe.SqlCeException     System.Data.SqlTypes.SqlTypeException     System.Drawing.Printing.InvalidPrinterException     System.EnterpriseServices.RegistrationException     System.EnterpriseServices.ServicedComponentException     System.ExecutionEngineException     System.FormatException     System.IndexOutOfRangeException     System.InvalidCastException     System.InvalidOperationException     System.InvalidProgramException     System.IO.InternalBufferOverflowException     System.IO.IOException     System.Management.ManagementException     System.MemberAccessException     System.MulticastNotSupportedException     System.NotImplementedException     System.NotSupportedException     System.NullReferenceException     System.OutOfMemoryException     System.RankException     System.Reflection.AmbiguousMatchException     System.Reflection.ReflectionTypeLoadException     System.Resources.MissingManifestResourceException     System.Runtime.InteropServices.ExternalException     System.Runtime.InteropServices.InvalidComObjectException     System.Runtime.InteropServices.InvalidOleVariantTypeException     System.Runtime.InteropServices.MarshalDirectiveException     System.Runtime.InteropServices.SafeArrayRankMismatchException     System.Runtime.InteropServices.SafeArrayTypeMismatchException     System.Runtime.Remoting.RemotingException     System.Runtime.Remoting.ServerException     System.Runtime.Serialization.SerializationException     System.Security.Cryptography.CryptographicException     System.Security.Policy.PolicyException     System.Security.SecurityException     System.Security.VerificationException     System.Security.XmlSyntaxException     System.ServiceProcess.TimeoutException     System.StackOverflowException     System.Threading.SynchronizationLockException     System.Threading.ThreadAbortException     System.Threading.ThreadInterruptedException     System.Threading.ThreadStateException     System.TypeInitializationException     System.TypeLoadException     System.TypeUnloadedException     System.UnauthorizedAccessException     System.Web.Services.Protocols.SoapException     System.Xml.Schema.XmlSchemaException     System.Xml.XmlException     System.Xml.XPath.XPathException     System.Xml.Xsl.XsltException 

Let's take a look at an example. Say that you create a new array with four elements and then try to access element 10. That will cause an IndexOutOfRangeException , which we can handle. First, we enclose the sensitive code that will cause an exception in a try block like this:

 
 class ch02_16 {   void Exceptor()   {     int[] array = new int[4];  try   {   array[10] = 4;   System.Console.WriteLine(array[10]);   }  .     .     .   }   static void Main()   {     ch02_16 obj = new ch02_16();     obj.Exceptor();   } } 

You can handle the exceptions that occur in this try block in a catch block that follows the try block, as you see in Listing 2.16. This catch block simply informs the user that an exception occurred by displaying the (unenlightening) message "Exception!" .

Listing 2.16 Using a try-catch Block (ch02_16.cs)
 class ch02_16 {   void Exceptor()   {     int[] array = new int[4];     try     {       array[10] = 4;       System.Console.WriteLine(array[10]);     }  catch   {   System.Console.WriteLine("Exception!");   }  }   static void Main()   {     ch02_16 obj = new ch02_16();     obj.Exceptor();   } } 

Here's what you see when you run this example:

 
 C:\>ch02_16 Exception! 

USING SYSTEM.CONSOLE.ERROR.WRITELINE

Although the code in our try/catch examples uses System.Console.WriteLine to display text because we're used to that method, it's actually more appropriate to display error text with System.Console.Error.WriteLine , which writes to the Error device (which need not be the console) if you have one specified. By default, System.Console.Error.WriteLine writes to the console just as System.Console.WriteLine does.


This works, but it doesn't tell the user a heck of a lot about what's going on. Let's go deeper into this topic.

Filtering Exceptions

You can specify which type of exception a catch block is to catch in C#. The generic catch block we saw in Listing 2.16 looks like this:

 
 catch {   System.Console.WriteLine("Exception!"); } 

You can specify which type of exception you want a catch block to catch by enclosing that type in parentheses after the keyword catch . Because all exceptions are based on System.Exception , that means that this catch block is the same as the previous one:

 
  catch(System.Exception)  {   System.Console.WriteLine("Exception!"); } 

This still doesn't tell us much, because this catch block will catch any exception, which means we still don't know which exception occurred. To catch only the IndexOutOfRangeException , we can use a catch block like this:

 
  catch(System.IndexOutOfRangeException)  {   System.Console.WriteLine("Array Index Exception!"); } 

This catch block only catches IndexOutOfRangeException exceptions. To catch not only IndexOutOfRangeException , but also any other kind that occurs, you can use multiple catch blocks, as you see in ch02_17.cs, Listing 2.17. The first catch block in that example catches IndexOutOfRangeException exceptions, and the second catches any other exception.

Listing 2.17 Filtering Exceptions (ch02_17.cs)
 class ch02_17 {   void Exceptor()   {     int[] array = new int[4];     try     {       array[10] = 4;       System.Console.WriteLine(array[10]);     }  catch(System.IndexOutOfRangeException)   {   System.Console.WriteLine("Array Index Exception!");   }   catch(System.Exception)   {   System.Console.WriteLine("Exception!");   }   }  static void Main()   {     ch02_17 obj = new ch02_17();     obj.Exceptor();   } } 

Here's what you see when you run ch02_17:

 
 C:\>ch02_17 Array Index Exception! 

Catching specific exceptions allows you to filter your exception handling, responding to individual exceptions individually. You can use as many catch blocks as you like, all catching different types of exceptions from a single try block.

When you filter your exceptions, here's something to keep in mindif exception A is based on exception B, and you want to catch both exceptions, make sure you catch exception B first. Here's an example to make this clear. The DivideByZeroException is based on the ArithmeticException class. If you arrange your catch blocks like this:

 
 catch(System.ArithmeticException) {   System.Console.WriteLine("Arithmetic exception!"); } catch(System.DivideByZeroException) {   System.Console.WriteLine("Divide by zero!"); } 

when a DivideByZeroException occurs, the first catch block will catch it, because DivideByZeroException is based on the ArithmeticException class. In fact, no exception can reach the DivideByZeroException catch block here (the compiler will let you know of this problem). To do this right, make sure you catch the exception that other exceptions are based on last, like this:

 
 catch(System.DivideByZeroException) {   System.Console.WriteLine("Divide by zero!"); } catch(System.ArithmeticException) {   System.Console.WriteLine("Arithmetic exception!"); } 

The finally Statement

Bear in mind that when an exception occurs in a try block, execution jumps from that try block to a catch block, which means that any code left in the try block won't execute. That can be a problem, because you might have done something in the try block that needs to be cleaned up. For example, you might have locked some resource or opened a file. That's where the finally block comes in. The code in the finally block is always executed (unless the program itself has halted) whether or not there has been an exception. In other words, the finally block is where you put your clean-up code, if you need it. You can use a finally block with or without catch blocks like so:

 
 try  try-block  [catch (  exception-declaration-1  )  catch-block-1  ]  .   .   .  [catch  (exception-declaration-n) catch-block-n  ] finally  finally-block  

Here are the parts of this statement:

  • try-block Contains the sensitive code that might cause an exception.

  • exception-declaration-1 , exception-declaration-n An exception object declaration used to filter the exceptions this catch block will handle.

  • catch-block-1 , catch-block-n Contains the exception handling code.

  • finally-block Contains the code you want executed whether or not there's been an exception.

You can see a finally block example in ch02_18.cs, Listing 2.18. In this example, the finally block ensures that the message "Ending..." is always displayed.

Listing 2.18 Using a finally Block (ch02_18.cs)
 class ch02_18 {   void Exceptor()   {     int[] array = new int[4];     System.Console.WriteLine("Starting...");     try     {       array[10] = 4;       System.Console.WriteLine(array[10]);     }     catch(System.IndexOutOfRangeException)     {       System.Console.WriteLine("Array Index Exception!");     }     catch(System.Exception)     {       System.Console.WriteLine("Exception!");     }  finally   {   System.Console.WriteLine("Ending...");   }   }  static void Main()   {     ch02_18 obj = new ch02_18();     obj.Exceptor();   } } 

Here's what you see when you run this code. Even though an exception occurred, the code in the finally block was indeed executed:

 
 C:\>ch02_18 Starting... Array Index Exception! Ending... 

FOR C++ PROGRAMMERS

Handling exceptions in C++ can cause problems with memory leaks when objects aren't cleaned up correctly and aren't deallocated in the finally block. That's changed in C#, which implements first-rate garbage collection. In fact, many programmers think that the new garbage collection facilities in C# are C#'s biggest step forward. We'll read about these facilities in the next chapter.


Working with Exception Objects

So far, we've used exceptions only in a simplistic way. We can filter various exceptions, but only to determine which one occurred. There is more to handling an exception, however. You can also use the properties of the System.Exception object to get more information.

Properties in C# are accessed with the dot (.) operator, just as methods are, but you specify them only by name , without passing arguments or using parentheses as you do with methods (although you can set up properties to take arguments). For example, the property exception .Message of the exception object exception holds a system message describing the exception. You can see the properties of exception objects in Table 2.1.

FOR C++ PROGRAMMERS

Properties in C# are like fields in C++ with built-in accessor methods; these methods let you restrict access to the actual data stored for each property.


Table 2.1. Public Properties of Exception Objects

PROPERTY

PURPOSE

HelpLink

Returns or sets a link to a help file associated with this exception.

Message

Returns a message that describes the current exception.

Source

Returns or sets the name of the application or the object that causes the error.

StackTrace

Returns a string showing the call stack at the time the current exception occurred.

TargetSite

Returns the method that caused the current exception.

To be able to refer to the current exception object and so gain access to its properties, you must name it in the catch block's declaration, just as you do an argument passed to a method. For example, here we're naming the exception object e , making it accessible inside this catch block:

 
  catch(System.IndexOutOfRangeException e)  {   System.Console.WriteLine(e.Message); } 

You can see how this works in ch02_19.cs, Listing 2.19, which displays the Message property of an exception object.

Listing 2.19 Using Exception Object Properties (ch02_19.cs)
 class ch02_19 {   static void Main()   {     ch02_19 obj = new ch02_19();     obj.Exceptor();   }   void Exceptor()   {     try     {       int[] array = new int[4];       array[10] = 4;       System.Console.WriteLine(array[10]);     }  catch(System.IndexOutOfRangeException e)   {   System.Console.WriteLine(e.Message);   }  } } 

You can see the system message for array index out-of-bounds exceptions when you run this example, like this:

 
 C:\>ch02_19 Index was outside the bounds of the array. 

Throwing Exceptions

When an exception occurs, it is thrown (which is why you use catch blocks to catch it). Using the throw statement, you can throw exceptions yourself; you don't need to rely on an actual exception to occur in your code. The throw statement looks like this:

 
 throw [  expression  ]; 

whereby expression is the exception object (can be omitted when rethrowing the current exception object in a catch clause).

You can see an example showing how this works in ch02_20.cs, Listing 2.20. In that example, we're purposely throwing an IndexOutOfRangeException exception in the try block, which is then caught in the following catch block.

Listing 2.20 Throwing an Exception (ch02_20.cs)
 class ch02_20 {   static void Main()   {     ch02_20 obj = new ch02_20();     obj.Exceptor();   }   void Exceptor()   {     try     {  throw new System.IndexOutOfRangeException();  }  catch(System.IndexOutOfRangeException e)  {       System.Console.WriteLine(e.Message);     }   } } 

Here's what you see when you run this code:

 
 C:\>ch02_20 Index was outside the bounds of the array. 

You can also use the throw statement to rethrow an exception. For example, you might catch an exception in a nested try-catch statement and if you want an outer catch block to handle the exception, you can just rethrow it. You can see how this works in ch02_21.cs, Listing 2.21.

Listing 2.21 Rethrowing an Exception (ch02_21.cs)
 class ch02_21 {   static void Main()   {     ch02_21 obj = new ch02_21();     obj.Exceptor();   }   void Exceptor()   {  try   {   try   {   int[] array = new int[4];   array[10] = 4;   }   catch(System.IndexOutOfRangeException e)   {   //Rethrow the exception   throw e;   }   }   catch(System.IndexOutOfRangeException e)   {   System.Console.WriteLine(e.Message);   }   }  } 

Here's what you see when you run ch02_21:

 
 C:\>ch02_21 Index was outside the bounds of the array. 

Note that if an exception handler cannot be found in the current method to handle an exception, C# automatically moves back to the method that called the present method to search for an appropriate catch block. If it can't find one there, it moves back one level again and keeps going until it does.

SHOP TALK : CATCHING EXCEPTIONS

It can be a pain trying to catch every possible exception your code can generate, but you should do it anyway. C# uses just-in-time debugging, and even if you compile your console applications so that they don't include debugging information, the user will still see a just-in-time debugger dialog box appear if there's been an unhandled exception. After dismissing that dialog box, the user will see a message about an unhandled exception and a stack trace, which is going to be very confusing and make you look unprofessional. So check on possible exceptions and handle them, even if only with a generic catch block.


Throwing Custom Exceptions

You can also create your own exception, based on the System.Exception class. We'll discuss how to derive classes from other classes in Chapter 4, but if you're familiar with the idea of inheritance, take a look at ch02_22.cs. In that example, we're creating a new exception class, TooHotException , based on the System.Exception class. After you create this new class, you're free to throw exceptions of that class, as in this example.

Listing 2.22 Throwing a Custom Exception (ch02_22.cs)
  public class TooHotException :   System.Exception   {   public TooHotException(string text):   base(text)   {   }  } public class ch02_22 {    public static void Main()    {      ch02_22 obj = new ch02_22();      obj.Exceptor();    }    public void Exceptor()    {      try      {  throw new TooHotException("Temperature is 97F!");  }  catch (TooHotException e)  {       System.Console.WriteLine(e.Message);     }   } } 

When you create an object of your custom exception class, you install a message in that object, like this: throw new TooHotException("Temperature is 97F!") . You can see that message displayed when the example runs:

 
 C:\>ch02_22 Temperature is 97F! 

All the details on class inheritance and deriving one class from another, as we've done here, are covered in Chapter 4.

FOR C++ PROGRAMMERS

C# strings are built on the .NET String type, which makes them quite different from C++ strings. For example, the C++ string class contains 24 methods (including overloaded versions) to search strings, all with the term find in their name. C# contains 18 methods (also including overloaded versions) to search strings, all with the term IndexOf in their names .


The final topic in this chapter is a big one in C#string handling. This is the last basic programming topic we'll need to grasp before going on to OOP.



Microsoft Visual C#. NET 2003 Kick Start
Microsoft Visual C#.NET 2003 Kick Start
ISBN: 0672325470
EAN: 2147483647
Year: 2002
Pages: 181

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net