The CLR allows an instance of any type to be thrown for an exception—from an Int32 to a String and beyond. However, Microsoft decided against forcing all programming languages to throw and catch exceptions of any type, so they defined the System.Exception type and decreed that all CLS- compliant programming languages must be able to throw and catch exceptions whose type is derived from this type. Exception types that are derived from System.Exception are said to be CLS-compliant. C# and many other language compilers allow your code to throw only CLS-compliant exceptions.
The System.Exception type is a very simple type that contains the properties described in Table 20-1. Usually, you will not write any code to query or access these properties in any way. Instead, when your application terminates due to an unhandled exception, you will look at these properties in the debugger or in a report that gets generated and written out to the Windows Application event log or crash dump.
TABLE 20-1Public Properties of the System.Exception Type
Property
Access
Type
Description
Message
Read-only
String
Contains helpful text indicating why the exception was thrown. The message is typically written to a log when a thrown exception is unhandled. Because end users do not see this message, the message should be as techni- cal as possible so that developers viewing the log can
use the information in the message to fix the code when
producing a new version.
Data
Read-only
IDictionary
A reference to a collection of key-value pairs. Usually, the code throwing the exception adds entries to this collec- tion prior to throwing it; code that catches the excep- tion can query the entries and use the information in its exception-recovery processing.
Source
Read/write
String
Contains the name of the assembly that generated the exception.
StackTrace
Read-only
String
Contains the names and signatures of methods called that led up to the exception being thrown. This property is invaluable for debugging.
TargetSite
Read-only
MethodBase
Contains the method that threw the exception.
HelpLink
Read-only
String
Contains a URL (such as file://C:\MyApp\Help.htm
#MyExceptionHelp) to documentation that can help a user understand the exception. Keep in mind that sound programming and security practices prevent users from ever being able to see raw unhandled exceptions, so unless you are trying to convey information to other programmers, this property is seldom used.
Property
Access
Type
Description
InnerException
Read-only
Exception
Indicates the previous exception if the current exception were raised while handling an exception. This read-only property is usually null. The Exception type also offers a public GetBaseException method that tra- verses the linked list of inner exceptions and returns the originally thrown exception.
HResult
Read/write
Int32
A 32-bit value that is used when crossing managed and native code boundaries. For example, when COM APIs return failure HRESULT values, the CLR throws an Exception-derived object and maintains the HRESULT value in this property.
I’d like to say a few words about System.Exception’s read-only StackTrace property. A catch block can read this property to obtain the stack trace indicating what methods were called that led up to the exception. This information can be extremely valuable when you’re trying to detect the cause of an exception so that you can correct your code. When you access this property, you’re actu- ally calling into code in the CLR; the property doesn’t simply return a string. When you construct a new object of an Exception-derived type, the StackTrace property is initialized to null. If you were to read the property, you wouldn’t get back a stack trace; you would get back null.
When an exception is thrown, the CLR internally records where the throw instruction occurred. When a catch block accepts the exception, the CLR records where the exception was caught. If, inside a catch block, you now access the thrown exception object’s StackTrace property, the code that implements the property calls into the CLR, which builds a string identifying all of the methods between the place where the exception was thrown and the filter that caught the exception.
The following code throws the same exception object that it caught and causes the CLR to reset its starting point for the exception.
private void SomeMethod() { try { ... }
catch (Exception e) {
...
throw e; // CLR thinks this is where exception originated.
// FxCop reports this as an error
}
}
In contrast, if you re-throw an exception object by using the throw keyword by itself, the CLR doesn’t reset the stack’s starting point. The following code re-throws the same exception object that it caught, causing the CLR to not reset its starting point for the exception.
private void SomeMethod() { try { ... }
catch (Exception e) {
...
throw; // This has no effect on where the CLR thinks the exception
// originated. FxCop does NOT report this as an error
}
}
In fact, the only difference between these two code fragments is what the CLR thinks is the original location where the exception was thrown. Unfortunately, when you throw or re-throw an exception, Windows does reset the stack’s starting point. So if the exception becomes unhandled, the stack loca- tion that gets reported to Windows Error Reporting is the location of the last throw or re-throw, even though the CLR knows the stack location where the original exception was thrown. This is unfortunate because it makes debugging applications that have failed in the field much more difficult. Some de- velopers have found this so intolerable that they have chosen a different way to implement their code to ensure that the stack trace truly reflects the location where an exception was originally thrown.
if (!trySucceeds) { /* catch code goes in here */ }
}
}
The string returned from the StackTrace property doesn’t include any of the methods in the call stack that are above the point where the catch block accepted the exception object. If you want the complete stack trace from the start of the thread up to the exception handler, you can use the System.Diagnostics.StackTrace type. This type defines some properties and methods that allow a developer to programmatically manipulate a stack trace and the frames that make up the stack trace.
You can construct a StackTrace object by using several different constructors. Some constructors build the frames from the start of the thread to the point where the StackTrace object is con- structed. Other constructors initialize the frames of the StackTrace object by using an Exception- derived object passed as an argument.
If the CLR can find debug symbols (located in the .pdb files) for your assemblies, the string re- turned by System.Exception’s StackTrace property or System.Diagnostics.StackTrace’s ToString method will include source code file paths and line numbers. This information is incredibly useful for debugging.
Whenever you obtain a stack trace, you might find that some methods in the actual call stack don’t appear in the stack trace string. There are two reasons for this. First, the stack is really a record of where the thread should return to, not where the thread has come from. Second, the just-in-time (JIT) compiler can inline methods to avoid the overhead of calling and returning from a separate method.
Many compilers (including the C# compiler) offer a /debug command-line switch. When this switch is used, these compilers embed information into the resulting assembly to tell the JIT compiler not to inline any of the assembly’s methods, making stack traces more complete and meaningful to the developer debugging the code.