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:
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!
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 ExceptionsYou 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 StatementBear 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:
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...
Working with Exception ObjectsSo 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.
Table 2.1. Public Properties of Exception Objects
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 ExceptionsWhen 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.
Throwing Custom ExceptionsYou 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.
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. |