System.Exception

System.Exception

System.Exception is the base exception class. All exceptions in .NET are derived from System.Exception. System.SystemException is the base class for system exceptions raised by the CLR, such as System.Data.DataException or System.FormatException. SystemException is derived directly from System.Exception. System.SystemException does not refine System.Exception. However, it is an important marker that distinguishes between system and application exception, as demonstrated in the following code:

using System;

namespace Donis.CSharpBook{
    public class Starter{
        public static void Main(){
            try {
                int var1=5, var2=0;
                var1/=var2;    // exception occurs
            }
            catch(Exception except) {
               if(except is SystemException) {
                   Console.WriteLine("Exception thrown by runtime");
               }
               else {
                   Console.WriteLine("Exception thrown by application");
               }
            }
        }
    }
}

System.Exception Functions

System.Exception has four constructors:

    public Exception1()
    public Exception2(string message)
    public Exception3(string message, Exception innerException)
    protected Exception4(Serialization info, StreamingContext context)

Exception1 is the default constructor. Exception2 constructor sets the user-friendly message of the exception. Exception3 constructor also sets the inner exception, which is the originating exception. Exception4 deserializes an exception raised remotely.

The Exception class has several other helpful functions. Table 9-1 lists the important methods of the class.

Table 9-1: Exception Methods

Method Name

Result

GetBaseException

Returns the root exception in a chain of exception objects

GetObjectData

Serializes data of the Exception class

GetType

Returns the type of the exception

ToString

Concatenates the name of the exception object with the user-friendly message

The following code calls GetBaseException and outputs the error message of the initial exception. If the current exception is the first exception in a chain of exceptions, GetBaseException returns null. Alternatively, you can walk InnerException properties back to the first exception.

using System;

namespace Donis.CSharpBook{
    public class Starter{
        public static void Main(){
            try {
                MethodA();
            }
            catch(Exception except) {
                Exception original=except.GetBaseException();
                Console.WriteLine(original.Message);
            }
        }

        public static void MethodA(){
            try {
                MethodB();
            }

            catch(Exception except) {
                throw new ApplicationException( "Inner Exception", except);
            }
        }

        public static void MethodB(){
            throw new ApplicationException("Innermost Exception");
        }
    }
}

System.Exception Properties

System.Exception has a full complement of attributes providing information on the exception. Table 9-2 describes the properties of the Exception class.

Table 9-2: Exception Properties

Property

Description

Type

Read/Write

Data

Returns a dictionary collection that provides optional user-defined details of exception.

IDictionary

R

HelpLink

Link to a help file for the exception.

string

R/W

HResult

The HRESULT, which is a 32-bit error code common to COM, assigned to the exception. This is a protected property.

int

R/W

InnerException

When exceptions are propagated, the inner exception represents the previous exception.

Exception

R

Message

User-friendly message describing the exception.

string

R

Source

Name of application or object where exception occurred.

string

R/W

StackTrace

String representation of the call stack when the exception occurred.

string

R

TargetSite

Reference to the method where exception is raised.

MethodBase

R

The Message and InnerException properties are settable in constructors of the Exception class.

The following code uses some of the properties of the Exception class. In Main, MethodA is called, and an exception is raised. The exception is caught and handled in Main. In the catch block, the exception flag is set to false. Leveraging the TargetSite property, MethodA is then called again successfully. The TargetSite property returns a MethodBase type, which can be used to late bind and invoke a method.

using System;
using System.Reflection;

namespace Donis.CSharpBook{
    public class Starter{
        public static bool bException=true;
        public static void Main(){
            try {
                MethodA();
            }
            catch(Exception except) {
                 Console.WriteLine(except.Message);
                 bException=false;
                 except.TargetSite.Invoke(null, null);
            }
        }

        public static void MethodA() {
            if(bException) {
                throw new ApplicationException("exception message");
            }
        }
    }
}

Application Exceptions

Application exceptions are custom exceptions and are thrown by the application, not by the CLR. Application exceptions are derived from System.ApplicationException or System.Exception. System.ApplicationException adds nothing to System.Exception. While System.SystemException is a marker for system exceptions, System.ApplicationException brands application exceptions. A custom exception derived from System.Exception accomplishes the same feat. When several custom exceptions are planned, create a custom base exception class to categorize the exceptions. For convenience and maintainability, deploy application exceptions together in a separate assembly.

Do not create an application exception for an existing exception. Research the available system exceptions to avoid replicating an existing exception.

These are the steps for creating an application exception:

  1. Name the application exception. As a best practice, the class name should have the Exception suffix, as in ApplicationException.

  2. Derive the application exception from System.Exception.

  3. Define constructors that initialize the state of the application exception. This includes initializing members inherited from the base class.

  4. Within the application exception, refine System.Exception as desired, such as by adding attributes that further delineate this specific exception.

To raise an application exception, use the throw statement. You can also throw system exceptions. Thrown exceptions are considered software exceptions. The CLR treats software exceptions as standard exceptions.

throw syntax:

throw exceptioninstance1;
throw2;

The second syntax is specialized: It is available in the catch block, but nowhere else. This version of the throw statement rethrows as an exception caught in the catch block. However, the best policy is to add additional context to an exception before propagating the exception object. Propagating exceptions is reviewed later in this chapter.

Application exceptions are typically prompted by an exceptional event. What is an exceptional event? A strict definition does not exist. You define the basis of the event using whatever criteria are appropriate. Remember, raising an exception simply for transfer of control or a nonexceptional event is bad policy. In an application, the following could be considered exceptional events where throwing an application exception is warranted:

  • Constructor fails to initialize the state of an object.

  • A property does not pass validation.

  • Null parameters.

  • An exceptional value is returned from a function.

ConstructorException is an application exception. Throw this exception when a constructor fails. It refines the System.Exception base class with name of the type and time of exception. In addition, the Message property is assigned a congruous message. This is the code for the ConstructorException class:

using System;

namespace Donis.CSharpBook{

    public class ConstructorException: Exception{

        public ConstructorException(object origin)
                : this(origin, null) {
        }

        public ConstructorException(object origin, Exception innerException)
                : base("Exception in constructor", innerException) {
            prop_Typename=origin.GetType().Name;
            prop_Time=DateTime.Now.ToLongDateString()+" "+
                DateTime.Now.ToShortTimeString();
        }

     protected string prop_Typename=null;
         public string Typename {
             get {
                 return prop_Typename;
             }
         }

         protected string prop_Time=null;
         public string Time {
             get {
                 return prop_Time;
             }
        }
    }
}

This code uses the ConstructorException class:

using System;

namespace Donis.CSharpBook{
    public class Starter{
        public static void Main(){
            try {
                ZClass obj=new ZClass();
            }
            catch(ConstructorException except) {
                Console.WriteLine(except.Message);
                Console.WriteLine("Typename: "+except.Typename);
                Console.WriteLine("Occured: "+except.Time);
            }
        }
    }

    class ZClass {
        public ZClass() {
            // initialization fails
            throw new ConstructorException(this);
        }
    }
}

Exception Translation

In some circumstances, the CLR catches an exception and rethrows a different exception. The inner exception of the translated exception contains the original exception. Invoking a method dynamically using reflection is one such circumstance. Exceptions raised in methods invoked by MethodInfo.Invoke are automatically trapped and converted to TargetInvocationExceptionNET documentation in MSDN will always confirm when exception translation will happen. Here is an example of exception translation:

using System;
using System.Reflection;

namespace Donis.CSharpBook{

    public class ZClass {
        public static void MethodA() {
            Console.WriteLine("ZClass.MethodA");
            throw new Exception("MethodA exception");
        }
    }

    public class Starter{
        public static void Main(){
            try {
                Type zType=typeof(ZClass);
                MethodInfo method=zType.GetMethod("MethodA");
                method.Invoke(null, null);
            }
            catch(Exception except) {
                Console.WriteLine(except.Message);
                Console.WriteLine("original: "+
                    except.InnerException.Message);
            }
        }
    }
}

COM Interoperability Exceptions

.NET applications often host COM components or expose managed components to COM clients. These applications must be prepared to handle and possibly throw COM exceptions, respectively. The prevalence of COM components makes COM interoperability an important consideration for managed applications into the foreseeable future.

COM Exceptions

COM components should sandbox exceptions, which protects COM clients from potential language-specific exceptions. COM methods return an HRESULT structure, which is the result code of the method. An HRESULT is a 32-bit structure, where the severity bit is the high-order bit. The severity bit is set if an exception is raised. The Win32 SDK defines constants representing various HRESULT codes. E_NOINTERFACE, E_INVALIDARG, E_OUTOFMEMORY, S_OK, and S_FALSE are common HRESULT codes. E_codes are error codes indicating that an exception was raised or some other exceptional event happened. S_codes are success codes where no failure occurred.

When managed components call methods on COM objects, the CLR consumes the resulting HRESULT. If the HRESULT represents a known COM error (E_code), the CLR maps the HRESULT to a managed exception. For example, E_POINTER maps to the NullReferenceException, which is a managed exception. An error code from an unknown HRESULT is mapped to a COMException object. No exception is thrown if the HRESULT is a success code (S_code). Table 9-3 shows the common translations of HRESULT to managed exceptions.

Table 9-3: COM Exception Table

COM Exception

Managed Exception

COR_E_OVERFLOW

OverflowException

COR_E_THREADSTOP

ThreadStopException

E_NOINTERFACE

InvalidCastException

E_NOTIMPL

NotImplementedException

E_OUTOFMEMORY

OutOfMemoryException

E_POINTER

NullReferenceException

The COMException is derived from System.Runtime.InteropServices.ExternalException, which indirectly derives from System.Exception. The COMException class has additional properties providing the details of the unknown COM exception. The ErrorCode property contains the unrecognized HRESULT from the COM method.

COM components implement Error objects to provide extended error information to clients. An Error object implements the IErrorInfo interface. Members of the IErrorInfo interface correlate to members of the COMException class and are therefore accessible to the managed client. Table 9-4 maps members of the Error object to the COMException class.

Table 9-4: IErrorInfo to COMException Mapping

IErrorInfo Member

COMException Member

IErrorInfo::GetDescription

COMException.Message

IErrorInfo::GetSource

COMException.Source

If IErrorInfo::GetHelpFile is non-zero,

IErrorInfo::GetHelpFile+"#"+IErrorInfo::HelpContext

COMException.HelpLink

The following code is a partial listing from an ATL project that publishes a COM component. The COM component exposes the CComponentZ::MethodA. Using the AtlReportError API, CComponentZ::MethodA builds an Error object to return extended error information to the client. The method also returns a custom error code in the HRESULT, which will be unknown to the CLR.

// ComponentZ.cpp : Implementation of CComponentZ

#include "stdafx.h"
#include "ComponentZ.h"
#include ".\componentz.h"

// CComponentZ

STDMETHODIMP CComponentZ::MethodA(void) {
   // TODO: Add your implementation code here

   HRESULT hResult=MAKE_HRESULT( 1, FACILITY_NULL, 12 );

   MessageBox(NULL, "COM component", "Hello from", MB_OK);

   return AtlReportError (GetObjectCLSID(), "My error message", 5,
         "http://error.asp",GUID_NULL, hResult);
}

The following code is managed code, in which the ATL component is called from a managed COM client. Because the HRESULT is unknown, the error code appears as a COMException exception.

using System;
using System.Runtime.InteropServices;

namespace COMClient {
   class Program {
         static void Main(string[] args) {
               try {
                    COMLib.CComponentZClass com_object =
                          new COMLib.CComponentZClass();
                    com_object.MethodA();
               }
               catch (COMException except){
                     Console.WriteLine(except.ErrorCode);
                     Console.WriteLine(except.HelpLink);
                     Console.WriteLine(except.Message);
               }
               catch (Exception) {

               }
         }
   }
}

Generating COM Exceptions

Managed components have an HRESULT property that translates a managed exception to a COM error result. System exceptions are already assigned an appropriate HRESULT. For application exceptions, you should initialize the HRESULT property in the constructor. It is important that managed components that expect COM clients set the HRESULT to a known COM error code.

TypeException is an application exception, which should be thrown when an object is the wrong type. TypeException has two overloaded constructors. Both constructors set the HResult property of the exception to the E_NOTIMPL error code (0x80004001). The one-argument constructor accepts a type object, which is the required type that was not implemented. The name of the type is added to the error message of the exception. This is the code for the TypeException class:

using System;

namespace Donis.CSharpBook{

    public class TypeException: Exception{

        public TypeException()
                : base("object type wrong") {
            HResult=unchecked((int) 0x80004001); // E_NOTIMPL
        }

        public TypeException(Type objectType)
                : base("Argument type wrong: "+objectType.Name
                    +" required"){
            prop_RequiredType=objectType.Name;
            HResult=unchecked((int) 0x80004001); // E_NOTIMPL
        }

        private string prop_RequiredType;
        public string RequiredType {
            get {
                return prop_RequiredType;
            }
        }
    }
}

The Delegator class uses the TypeException class. Delegator delegates method calls of Delegator .MethodA to an external object. For the delegation to be successful, the external object must also implement the MethodA method, which is defined in the ZInterface. Appropriately, the code in Delegator.MethodA confirms that the external object implements the ZInterface. If not, the TypeException is thrown. Otherwise, Delegator.MethodA proceeds with the delegation:

using System;

namespace Donis.CSharpBook{
    interface ZInterface {
        void MethodA();
    }

    public class Delegator {
        public Delegator(object obj) {
            externalobject=obj;
        }

        public void MethodA() {
            if(externalobject is ZInterface) {
                ((ZInterface)externalobject).MethodA();
            }
            else {
                throw new TypeException(
                    typeof(ZInterface));
            }
        }

        private object externalobject;
    }
}

YClass creates an instance of the Delegator class. A ZClass object is passed into the Delegator constructor as the external object. ZClass does not implement MethodA. Delegator.MethodA is called in YClass.UseDelegator. A TypeException is thrown because the ZClass does not implement the appropriate interface:

using System;
using System.Runtime.InteropServices;
using Donis.CSharpBook;

class ZClass {
}

[ClassInterface(ClassInterfaceType.AutoDual)]
public class YClass{
    public void UseDelegator(){
        ZClass obj=new ZClass();
        Delegator del=new Delegator(obj);
        del.MethodA();
    }
}

COM clients can access managed code through COM Callable Wrappers (CCWs). The following unmanaged code creates an instance of YClass and invokes YClass.UseDelegator. As expected, an exception occurs, which translates to E_NOTIMPL in unmanaged code. The COM client checks for this exception and displays the appropriate message.

#import "..\yclass.tlb" no_namespace, raw_interfaces_only, named_guids

#include "objbase.h"

void main() {
    CoInitialize(NULL);
   _YClassPtr obj(CLSID_YClass);
   HRESULT hResult=obj->UseDelegator();
   if(hResult==E_NOTIMPL) {
         MessageBox(NULL,"Required interface not implemented",
               "In Managed Component", MB_OK);
   }
   else {
         MessageBox(NULL, "Managed Component", "No error",
               MB_OK);
   }
}