Exceptions Overview

At the base of all exception classes is System.Exception, which derives from System.Object.

Note

It is possible to throw a System.Object directly from System.Object, but doing so is not consistent with how exceptions are used in the CLR. In addition, the C# compiler doesn't allow it.


Most (if not all) of the exception classes derived from System.Exception do not add functionality, but they do provide a type so that errors can be filtered and handled in the correct context as discussed previously. Many classes are derived directly from System.Exception. A couple of classes that derive directly from System.Exception are IOException and WebException. IOException is thrown when an I/O error occurs (usually the result of an operation performed in the System.IO namespace). WebException is thrown when an error occurs when accessing the network using WebRequest or WebResponse. Many other classes derive directly from System.Exception, but the two most important are ApplicationException and SystemException.

One of the more interesting classes of exceptions derived from SystemException is the ExternalException class. The COMException class is derived from ExternalException. When CLR interop turns HRESULTS into exceptions, it builds a COMException. If you are using P/Invoke to interop with Win32 and the API returns a Win32 error, then it is turned into a Win32Exception, which is also derived from ExternalException. These Win32 errors are like ERROR_FILE_NOT_FOUND and ERROR_ACCESS_DENIED. Unmanaged Structured Exception Handling (SEH) exceptions are turned into an SEHException, which is also derived from ExternalException.

Exceptions can be split into two basic categories: runtime-based exceptions and object-based exceptions. These could be loosely referred to as those exceptions that are explicitly thrown (object-based exceptions) and those that are not (runtime-based exceptions).

The runtime throws a SystemException when the runtime has detected an error such as divide-by-zero, overflow, array-bounds check, or accessing a null pointer. The runtime-based exceptions are usually derived from the SystemException class. Many exception classes are derived from SystemException. Some of these exceptions are thrown as a result of a catastrophic failure such as ExecutionEngineException (thrown due to a failure in the CLR) and StackOverflowException (thrown due to execution stack overflow, probably because of deep or unbounded recursion). These exceptions in general should not be caught, and it is not good programming practice to throw a SystemException. A prime example of a runtime-based exception would be a DivideByZeroException. The DivideByZeroException is thrown when an attempt is made to divide an integral or decimal value by zero. (Note that floating-point numbers never generate exceptions.) These types of exceptions are like those thrown by the Win32 Structured Exception Handling mechanism. Listing 15.1 shows some unmanaged code that generates these types of faults. The code for this sample is in the SEHException directory.

Listing 15.1. Catching SEH Exceptions
void SEHExceptions()
{
    __try
    {
        int a = 0;
        int b = 1;
        int c = b/a;
        std::cout << "The result is: " << c << std::endl;
    }
    __except( EvalException(GetExceptionCode(), GetExceptionInformation()) )
    {
        std::cout << "Caught exception" << std::endl;
    }
    __try
    {
        int *p = 0;
        std::cout << "The value is: " << p[1] << std::endl;
    }
    __except( EvalException(GetExceptionCode(), GetExceptionInformation()) )
    {
        std::cout << "Caught exception" << std::endl;
    }
}

The sample in Listing 15.1 generates two exceptions: a divide-by-zero exception (which corresponds to a managed DivideByZeroException) and an access violation exception (the equivalent in managed code would be a NullReferenceException). Listing 15.2 shows how this would be done with regular exception handling.

Listing 15.2. Catching EH Exceptions
class custom_exception : public std::exception
{
public:
    explicit custom_exception(const std::string& _message) : message(_message)
    {
    }

    virtual ~custom_exception()
    {
    }

    virtual const char *what()
    {
        return (message.c_str());
    }

private:
    // The message
    std::string message;
};


void EHExceptions()
{
    try
    {
        int a = 0;
        int b = 1;
        int c = b/a;
        std::cout << "The result is: " << c << std::endl;
    }
    catch(std::exception)
    {
        std::cout << "Caught EH exception " << std::endl;
    }
    catch(...)
    {
        std::cout << "Default caught EH exception " << std::endl;
    }

    try
    {
        int *p = 0;
        std::cout << "The value is: " << p[1] << std::endl;
    }
    catch(std::exception)
    {
        std::cout << "Caught EH exception " << std::endl;
    }
    catch(...)
    {
        std::cout << "Default caught EH exception " << std::endl;
    }
    try
    {
        throw custom_exception("This is a test");
    }
    catch(custom_exception& e)
    {
        std::cout << "Caught custom exception: " <<  e.what() << std::endl;
    }
}

In addition to the two runtime exceptions shown in Listing 15.1, Listing 15.2 also shows the definition and throwing of a custom exception.

The output for the code in Listing 15.1 and Listing 15.2 (both SEH exceptions and EH exceptions) is shown in Figure 15.1.

Figure 15.1. Catching managed exceptions.


This sample shows how one would catch runtime-based exceptions in unmanaged code. It is important to understand where exceptions came from to appreciate what the CLR and the .NET Framework adds to handling exceptions. One significant advantage to using managed code is that the runtime-based exceptions are merged with the object-based exceptions. You might argue that EH or C++ exception handling does merge SEH and EH because you can catch divide by zero errors and access violations (null reference). However, a large gap exists in the functionality and information that is available with each of the exception-handling schemes. For example, with SEH, you can get a snapshot of the CPU registers at the time of the exception. With SEH, you can also retrieve the address where the exception occurred. No single unifying exception-handling scheme exists. With managed runtime-based exceptions, a single exception handling scheme is available that is quite powerful.

Object-based exceptions are exceptions that are generated by the .NET Framework classes to indicate various errors in casting, security, argument type, and so on. Some of the major exceptions in the two categories are listed next:

Following are the runtime-based exceptions:

  • ArithmeticException— Represents a class of exceptions. Currently DivideByZeroException, NotFiniteNumberException, and OverflowException are derived from this exception.

  • DivideByZeroException— Thrown when an integer is divided by zero. For example

    int a = 1;
    int b = 0;
    int c = a/b;
    
  • NotFiniteNumberException— Thrown when a floating-point number is positive infinity, negative infinity, or Not-a-number (NaN). Applications in C# will not throw this exception. This exception is thrown for languages that do not support infinity and Not-a-number values. This exception is derived from ArithmeticException.

  • OverflowException— Derived from ArithmeticException this exception is thrown at runtime when an attempt is made to write a value that is larger than that largest possible value on the receiving object. For example:

    byte a = 0;
    int b = 900;
    checked {
        a = (byte)b;
    }
    
  • ExecutionEngineException— This error is a fatal error that should never occur. It is usually the result of corrupted or missing data when trying to execute code.

  • StackOverflowException— Thrown when the call stack has filled up usually due to deep or unbounded recursion. For example

    static void StackOverflow()
    {
        StackOverflow();
    }
    . . .
    StackOverflow();
    
  • NullReferenceException— Thrown when access to an object that has a null value is attempted. For example

    object o = null;
    o.ToString();
    

The object-based exceptions are as follows:

  • TypeLoadException— The online documentation indicates that this exception is thrown when the CLR cannot find the assembly or the type within the assembly, or it cannot load the type. You might get a FileNotFoundException for assemblies that cannot be found (using Assembly.Load) and a null Type for types that cannot be found.

  • IndexOutOfRangeException— This is thrown when the runtime detects that an array is indexed improperly. For example:

    arr[arr.Length + 10];
    
  • InvalidCastException— This is thrown when the runtime cannot cast from one form of the execution to another. For example:

    int n = 5;
    object o = n;
    string s = (string)o;
    
  • InvalidOperationException— This is thrown when a method's call is invalid for the object's current state. For example:

    foreach(int i in a)
    {
        Console.WriteLine("Enum: {0} ", i);
        if(i == 4)
            a.Remove(4);
    }
    
  • MissingFieldException— Normally if you try to access a field that is not defined, you get a compile-time error. You get a MissingFieldException if you modify one assembly and remove a field that is referenced in another assembly without recompiling and linking the assemblies as a unit. This exception occurs when the method that accesses the missing field is JITd (just-in-time compiled) so the exception can be generated without information as to where in a method from which this exception is generated.

  • MissingMethodException— Similar to MissingFieldException, this exception is thrown when an assembly is modified independent of a dependent assembly. Also, like MissingFieldException, this exception is thrown when the method that references the missing method is JITd.

  • SecurityException— This exception is thrown as the result of a failed security check. Listing 15.3 shows an example of this type of exception. Chapter 16 provides more details on security issues.

Listing 15.3. Illustration of a SecurityException
[DllImport("user32.dll")]
public static extern int MessageBox(int hWnd, string text,
                                    string caption, uint type);

static void SecretMethod()
{
    // Create a security permission object to describe the
    // UnmanagedCode permission:
    SecurityPermission perm =
        new SecurityPermission(SecurityPermissionFlag.UnmanagedCode);

    // Deny the UnmanagedCode from our current set of permissions.
    // Any method that is called on this thread until this method
    // returns will be denied access to unmanaged code.
    perm.Deny();
    MessageBox(0, "Hello from my secret method. Shhh . . .", "Top Secret", 0);
}
. . .
try
{
    SecretMethod();
}
catch(Exception e)
{
    // Dump interesting exception information
    Console.WriteLine (e.ToString());
}

This is not an exhaustive list of all of the exceptions that can be thrown, but it is meant to show the types of exceptions that could be runtime-based or object-based exceptions.

The user-defined exception could be considered a third type or category of exception. Although nothing specifically denies a programmer from deriving from any of the exception classes (they are not sealed in C# terminology), it is highly recommended that the user-defined exceptions derive from the ApplicationException class.

When a custom exception is required for your application, you should derive that custom exception from ApplicationException. It is strongly recommended that a custom exception class not be derived directly from the Exception class. This would only make the application harder to understand and maintain. The Exception class is the base class for a huge range of exceptions. Consider the problem if you were trying to guess a particular animal and you were only told that it was a mammal rather than a cat or dog. Putting your application custom exceptions in the ApplicationException category narrows the list of errors that could have caused the exception.

Now you know that exceptions are good and return codes are bad. You also know about some of the exception types that can be thrown and have a general idea of the source of exceptions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.89.24