Common Exceptions


System.Exception is the root class of all exception types. It provides only the most common information that any exception would be expected to have. You can create a new Exception by using one of three primary constructors: Exception(), a standard no-argument constructor; Exception(string message), which enables you to supply some information about the error that has occurred; or Exception(string message, Exception innerException), which, in addition to capturing a message, enables you to wrap another exception as an inner exception. All of this information is subsequently available through Exception instance properties.

Three other interesting properties are available: StackTrace, CallSite, and HResult. StackTrace is simply a textual representation of the method call trace that generated the exception, complete with source line numbers (if debugging information is available). CallSite gives you an easy way to retrieve the method information that generated the exception. Lastly, HResult enables exceptions to wrap COM HRESULTs, and is auto-populated by much of the COM interoperability layer when marshaling an error from COM into a managed Exception.

Note

Note that the StackTrace property is simply a formatted string that represents the call-stack leading up to the exception. If you need access to structured call-stack data, you can construct a new System.Diagnostics.StackTrace object, passing in the given Exception as an argument. This type gives you a much richer view into the call-stack.

Lastly, the Data property allows storage of arbitrary data along with an exception. It is simply an IDictionary instance that stores arbitrary key-value pairs that might track related information about the cause or details of an error. The IDictionary interface is fully explained in Chapter 6.

In addition to the Exception type, there are quite a few common exceptions you will find in the Framework. We'll take a quick look at them.

System Exceptions

The following set of exceptions is different from any others simply because you should rarely attempt to catch or handle them. So, why even discuss them? Quite simply: if you see them being thrown, there are likely critical problems either in your code or in the system surrounding your code. You should respond by trying to find the source of the problem, usually a bug in your code, not by catching one of them and letting your program continue to limp along.

Not only should you seldom try to handle any of these, you shouldn't throw them either. Use one of the other standard types outlined below or create your own custom exception class.

OutOfMemoryException

This exception indicates that a request to allocate memory has failed, called an out of memory (OOM) condition. This type doesn't provide any additional useful properties or methods on it, and you should seldom try to catch this exception. Letting your application fail is typically the best thing you can do under these circumstances. As discussed in Chapter 3, an OOM could be the result of overall system pressure or just a failure to allocate some ridiculous request for memory (e.g., new string[int.MaxValue]). If you suspect the OOM is a result of the latter, you might attempt to catch it and proceed (e.g., when hosting arbitrary third-party code). But realize that doing so might cause other OOMs to occur if you were mistaken and it was really a case of the former situation. There is no way to programmatically distinguish between the two.

StackOverflowException

In version 1.0 and 1.1 of the CLR, a StackOverflowException would be generated when a thread's stack space was entirely consumed. In version 2.0, stack overflow (SO) policy has changed to perform a fail fast by default. This policy along with a description of fail fast can be found in Chapter 3.

NullReferenceException

This exception will be thrown by the runtime if you try to use a null reference as though it pointed to a real, live object. This is usually indicative of a programming error, for example, forgetting to check for null before using the input arguments to a method or forgetting to set a variable to an instance before using it. Prior to version 2.0 of the Framework, an access violation (caused by Windows page protection, usually a bug in some unmanaged code being interoperated with) would show up as a NullReference Exception. Now such code generates an AccessViolationException so that you can distinguish between the two. (Note that Windows 98 still causes NullReferenceExceptions for these situations.)

InvalidCastException

If you try to perform an invalid cast (i.e., with the castclass IL instruction), it will fail at runtime by throwing an instance of InvalidCastException. This includes performing invalid casting primitives (e.g., casting a bool to a double) or trying to reference an object using a variable incompatible with its runtime type.

IndexOutOfRangeException

An IndexOutOfRangeException typically indicates that you've attempted to access an indexable object using an index which is out of bounds. For example, accessing position -1 or position 5 on an array with 5 elements will fail. This is often a result of "off by one" programming mistakes and, again, almost always represents a bug in the program. Other APIs throw this exception; for example, Console.WriteLine will throw this if you've mismatched your formatting arguments. These types of bugs can often be tricky to track down.

ArithmeticException

There are a couple exceptions that derive from ArithmeticException and that are thrown by the CLR during certain mathematical operations:

  • DivideByZeroException: This will be thrown by the CLR if you attempt to divide an integral or decimal by zero, which is obviously an undefined operation in traditional mathematics.

  • OverflowException: This will be thrown if the results of a numeric assignment, cast, or conversion would result in an overflow, for example if you try to store a number larger than can be held by the defined range of a type. This only happens when executing in a checked block, denoted by the checked keyword in C#. Under normal circumstances, that is, when not in a checked context, underflow or overflow will wrap around without error.

RuntimeWrappedException

As discussed in Chapter 3, any objects thrown via the IL throw instruction that do not derive from Exception get wrapped in a System.Runtime.CompilerServices.RuntimeWrappedException. This only occurs for assemblies that are annotated with the RuntimeCompatabilityAttribute, where WrapNonExceptionThrows has been set to true. The original exception is accessible through the WrappedException property.

Others Critical System Errors

Other system exceptions not discussed in depth here are as follows: ExecutionEngineException, AccessViolationException, InvalidAddressException, TypeLoadException, MissingField Exception, and MissingMethodException. If some of these look scary, they should. Most indicate critical system failures. If you see them, you've probably uncovered a bug in the CLR (or in your IL). SecurityException is thrown by Code Access Security infrastructure if code attempts to perform operations for which it hasn't been granted access. Please refer to Chapter 9 for details.

Other Standard Exceptions

The following exceptions are relatively common to run into when using both Framework and custom APIs. Therefore, you should know how to deal with them and what error conditions they are used to indicate. You should also feel free to throw these in your own code where appropriate.

ArgumentException

This is an all-purpose exception that indicating some argument passed to an operation is invalid. This could happen, for example, if the input does not satisfy a method's preconditions or if there is corruption detected on an object referenced by a parameter. Seldom should an application try to catch any of these. They represent a bug in the caller of the method, for example that it must ensure to initialize state properly, verify the integrity of incoming data, and so forth.

This type offers a range of constructors that enable you to set its two instance properties, Message and ParamName. Message is simply a description of the problem, while ParamName enables you to specify precisely which parameter the problem applies to. Use one of ArgumentException's subclasses if they are more descriptive for your situation.

ArgumentNullException

As mentioned in the system exceptions section, you should never access a null reference blindly (causing a NullReferenceException to be leaked out of your code); this is especially true when arguments accepted from within a public API. In these cases, you should explicitly check for and throw an ArgumentNullException in when a null pointer is passed in. For example, consider this code:

 public void DoSomething(object someObject) {     if (someObject == null)         throw new ArgumentNullException("someObject");     // Ok to proceed... } 

If a null someObject is supplied, an ArgumentNullException is thrown that contains the Message "someObject cannot be null"; this is substantially more helpful than a NullReferenceException.

ArgumentOutOfRangeException

This exception indicates that an argument has been supplied that is not in the valid range. This could happen when a method expects a number within a finite range, for example, or if arguments relate to each other in a precise manner. This exception also takes the parameter name and uses it to construct a meaningful Message for clients. You should prefer throwing this rather than IndexOutOfRangeException in your own code.

InvalidOperationException

If program or instance state is inconsistent, such that a requested operation cannot complete correctly, an InvalidOperationException should be thrown. This might happen if object invariants don't hold or if an object has yet to be initialized, for example.

ObjectDisposedException

ObjectDisposedException derives from InvalidOperationException and should be used if an attempt is made to perform work with an already Disposed object. Imagine an object that encapsulates access to a file. If an instance was created, used, and subsequently closed the file, clearly accessing the object is invalid. Attempts to invoke file-based operations on this object from this point on must be detected; the object should respond by throwing an ObjectDisposedException.

NotImplementedException and NotSupportedException

Both of these exceptions indicate that a type does not provide a functional implementation of some requested behavior. The difference between the two is that NotImplementedException implies that an implementation is not currently present but that there will be one in the future. This can be used when implementing an interface during development. If you don't have time to fully implement the interface, throwing a NotImplementedException will permit you to compile, run, and test partial functionality.

NotSupportedException, on the other hand, makes an explicit statement that an operation isn't supported by a type and that it won't be for the foreseeable future (i.e., never). This can likewise be used for partial interface implementation.

Custom Exceptions

Writing your own custom exception is worthwhile and reasonably easy to do. Although Exception gives you a Message property with which to convey details about an error condition, you will often need to catch specific exceptions (avoiding excessive try {...} catch (Exception e) {...} style code, which consequently can swallow critical system exceptions) or provide additional structured information about the failure.

A custom exception can be created by subclassing System.Exception and providing the additional properties and constructors to capture the important information at the time of failure. Each exception should provide at least the four basic constructors that System.Exception supplies:

 class CustomException : Exception {     public CustomException() : base() {}     public CustomException(string message) : base(message) {}     public CustomException(string message, Exception innerException) :         base(message, innerException) {}     protected CustomException(SerializationInfo info,         StreamingContext context) : base(info, context) {} } 

This last constructor is necessary to ensure that your custom exception can be serialized. This is necessary to ensure serialization across AppDomain boundaries, for example.

Any custom exception types should be well factored into a type hierarchy that makes sense. System.IO .IOException is a great example of a well-designed exception hierarchy in the Framework. You can get as specific or remain as vague as you wish while catching exceptions. Note that you should not derive from ApplicationException or SystemException. Neither adds any value whatsoever. Real code will not catch either one of these; deriving from them just adds an unnecessary depth into your exception hierarchy.




Professional. NET Framework 2.0
Professional .NET Framework 2.0 (Programmer to Programmer)
ISBN: 0764571354
EAN: 2147483647
Year: N/A
Pages: 116
Authors: Joe Duffy

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