When writing any piece of software, at some stage errors occur when the application is run. This is through no fault in the programming of the code, but rather through unexpected actions on the part of the user. Unexpected actions may include selecting a file that does not exist or entering a letter where a number is expected.
These unexpected actions can be handled in one of two ways, either by preemptive programming or by using exceptions. The .NET Framework provides an Exception
class in the System
namespace to handle exceptions. Preemptive programming requires the programmer to think about everything that could possibly go wrong when the application is run. Let us consider the first of the two examples cited above. To find out if a file exists, we can invoke the
Exists()
function of the File
class, passing to it the string supplied to us by the user: if ( !File.Exists (file_name))The
{
// The source file does not exist
}
else
{
// The source file exists
}
if
statement catches the error; but, what should we do with it? What about if the if
statement was not inverted? This would cause a programming bug that would not be caught until runtime and would cause an exception. When a program error occurs, either the system or the currently executing application reports it by throwing an exception containing information about the error. Once thrown, an exception is handled by the application or by the default exception handler. Even if you remove all bugs and anticipate all user errors, you will still run into predictable but unpreventable problems, such as running out of memory or attempting to open a file that no longer exists. You cannot prevent exceptions, but you can handle them so that they do not bring down your program.
[edit]
Benefits of exceptions
Exceptions provide the ability to handle or clean up code in a localized place. Also, they allow clean-up code to execute in case of error. The application becomes easier to write and maintain when clean-up code is in a centralized place.Also—and maybe more importantly—, exceptions can be used to find bugs within the code; because, the CLR walks up the stack of the thread to get the call history. With this, it is possible to find the location of the failure within the code. Further, it is possible to add additional information within the exception, so that a developer can describe the kind of error more precisely.
The biggest problem with exceptions is performance because of some information overhead. So, they should be used only where preemptive programming is inappropriate. Unfortunately, most developers tend to use exceptions improperly—i.e.
catch (Exception e);
which will be discussed later—or too seldom, so debugging proves harder. [edit]
Using exceptions
Execptions are used within thetry
-, catch
-, and finally
-code blocks. The Exception
is created and thrown within the try
-block. If the
Exeption
is thrown, the code within the try
-block stops and the finally
-block executes. After this, the first match to a sequence of
catch
-blocks gets executed, bringing the application into a stable state; because, the code in the try-block didn't finish. The structure of this looks like the following:
void example()The
{
try
{
// Here is where the Exception would be thrown
}
catch (ArgumentOutOfRangeException e)
{
// This block executes if the Exception is of type
// ArgumentOutOfRangeException. Otherwise, the program
// jumps to the next catch-block.
}
catch (InvalidCastException e)
{
// This block executes if the Exception is of type
// InvalidCastException. The Exception is stored
// within the Variable "e". This is needed e.g. to query
// and extend the Error Message of the Exception.
}
catch (Exception e)
{
// Because all Exceptions in .NET derive from Exception,
// this block will handle all .NET Exceptions.
// This kind of Exception cannot be handled and must be
// rethrown. Think of an OutOfMemoryException, where
// creating a Window showing the Exception makes it even
// worse.
throw;
}
catch
{
// Catches all kinds of exceptions, even when they are not
// CLS-comform, which can appear only when calling
// unmanaged code.
// This can not be handled correctly, either.
throw;
}
finally
{
// This code will execute in any case,
// whether an Exception was thrown or not.
}
}
catch
block can handle the Exception
in one of three ways: - Rethrow the same exception to notify the code which is prior in the call stack.
- Throw another exception with more precise information. The current
Exception
can be appended. - Handle the exception and continue with the code after the
try
/catch
/finally
-block.
- Never handle an
Exception
of typeSystemException
, because this is not—or, at least, hardly—possible. Examples are:OutOfMemoryException
- Occurs when the CLR has no free memory available to create new objects on the Heap. Even the garbage collector does not help at this point. The best thing you can do is to terminate the program (thread). If you want to develop a server application, it is best to develop also a watch dog program or to register the program as a Windows service, so that it will restart the program or even the entire server.
StackOverflowException
- Similar to the
OutOfMemoryException
; but this time, there is no space on the Stack. This makes it also impossible to call thefinally
-block. Therefore the program can be in an undefined state and should be terminated.
- Similar to the
ExecuteEngineException
- This
Exception
throws when the CLR detects an error like a corrupted data-structure (e.g. a buffer overflow) or a bug within the CLR itself. Anyway, in that case, the CLR will call a debugger or terminate the whole process without executing anycatch
- orfinally
-blocks.
- This
- Also, catching such an
Exception
and checking the type of it afterwards is possible; but, it is not a pretty programming style.
[edit]
The System.Exception
class
The System.Exception
class is small class representing information about the kind of Exception
. The properties are: Message
- A string representing a human-readable message.
Source
- The name of the assembly where the exception was thrown. Not available in the Mobile .Net Framework.
StackTrace
- Names of the calling functions as found on the call stack.
TargetSite
- The method that throws the
Exception
originally. Not available in the Mobile .Net Framework.
- The method that throws the
HelpLink
- A link to a resource (e.g. a Web site or a help file) which provides further information. Not available in the Mobile .Net Framework.
InnerException
- This field is normally set to
null
. If not, this indicates that thisException
was thrown because of anotherException
which is now stored within this property. Additional to this property, you can also use theGetBaseException()
function which returns the innermostException
.
- This field is normally set to
HResult
- This is the
HResult
which will get returned from a COM object. This property is only needed when interoperating with COM code.
- This is the
[edit]
Handling exceptions
As stated before we have three possibilities to handle aException
: - Rethrow
- Throw new exception
- Handle the exception
[edit]
Rethrow an Exception
One way to use Exception
is to catch an Exception
and run exception-specific code. When this is done, the same exception is thrown again and "bubbling up" the call stack. public String ReadFirstLine (String FileName)Even if we catch a
{
try
{
FileStream fs = new FileStream (FileName, FileMode.Open);
}
catch (Exception e)
{
// override the Message of the Exception
e.Message = "Could not open the file.";
// rethrow the same exception
throw;
}
// the file is open now
try
{
String FirstLine = fs.ReadLine();
}
catch (Exception e)
{
e.Message = "Could not read first line of file.";
throw;
}
finally
{
// close the FileStream in each case
fs.Close();
}
return FirstLine;
}
System.Exception
here, it is not a problem, because we can simply rethrow it. Another problem you should respect within catch(Exception e)
blocks is that you should avoid creating new objects within the heap (reference types). Remember the OutOfMemoryException
example. Sometimes, it is necessary to set data back to its original state when an exception occurs. In that case, you create a backup of the properties you want to change and restore them in case of a exception.
private Byte[10] ReadNextTenBytes(FileStream fs)
{
Int64 PositionBeforeOutput = fs.Position;
try
{
var Output = new Byte[10];
fs.Read (Output, fs.Position, 10);
}
catch
{
fs.Position = PositionBeforeOutput;
throw;
}
return Output;
}
[edit]
Throw a new Exception
Sometimes the Exception
thrown by a function does not describe the core of the problem well enough. In such a case, you catch the exception and throw a more describing one instead. public Int32 Inverse(Int32 Number)Notice that when throwing a new exception or when rethrowing the original
{
Int32 Inverse;
try
{
Inverse = 1 / Number;
}
catch (DivideByZeroException e)
{
ArgumentOutOfRangeException ae =
new ArgumentOutOfRangeException (
"Number can't be Zero", e);
throw ae;
}
return Inverse;
}
Exception
using code like catch (Exception e){throw e;}
properties like StackTrace
and Source
will get overridden by the information of the current function. Also, in most cases, you should set the
InnerException
property (second argument of the constructor) for further reference. [edit]
Handle the Exception
In some special cases, it is possible to bring the application into a stable state, even when an Exception
has occurred. public Int32[] ParseNumbers(String[] Numbers)This code takes an array of integers which are presented as a
{
Int32[] ParsedNumbers = new Int32[];
Int32 ParsedNumber;
int elements = 0;
foreach (Int32 Number in Numbers)
{
try
{
ParsedNumber = Int32.Parse(Number);
}
catch (FormatException e)
{
continue;
}
catch (OverflowException e)
{
continue;
}
ParsedNumbers[elements++] = ParsedNumber;
}
return ParsedNumbers;
}
String
array. The code tries to parse every string within this array one after the other. If this fails, the Int32.Parse()
method will probably throw an Exception
of type FormatException
or OverflowException
. This means the string is not parseable. This does not matter in that case; and, we simply repeat with parsing the next String
in the array. Other exceptions will not be handled. They continue bubbling up the call stack.
Notice, that in this example, we do not need access to the
Exception
; so, we could rewite, e.g. catch (FormatException e)
as catch(FormatException)
. [edit]
Catch
filter
In C#, the catch
block filters for a specific type of Exception
and runs the first match. But, the .NET CLR supports also more complex catch filters which you can use from Visual Basic .NET, C++ with managed extensions, or IL. In VB .Net this might look like:
Catch e as Exception When x = 0In that example, the
catch
code executes only if the Exception
is of type System.Exception
and the variable x
is equal to zero. In C#, you need to rewrite this using an additional if
block. [edit]
Creating and throwing an Exception
The first thing when wanting to create a Exception
is to find the best fitting one for your purpose. Therefore you can use the List Exceptions Program to find all the common ones. Then, read the result and select one. If you want to be more specific by providing a more meaningful name or additional properties, derive a new Exception from the next best fitting one. Note, that in the worst case the best fitting Exception is not
System.Exception
or System.SystemException
, which is a reserved behavior for the .NET Framework internal exceptions from which you should derive, but System.ApplicationException
. Custom exceptions should always be serializeable, so that the exception can also used in an distributed environment like a Web service.
If you decided on one or created an exception, you can create a new exception like any other class. Then you simply throw that
Exception
using the throw
-Keyword. For example:
ArgumentOutOfRangeException e =or
new ArgumentOutOfRangeException();
e.Message = "Value must be within 1 and 100";
throw e;
ArgumentOutOfRangeException e =or
new ArgumentOutOfRangeException
("Value must be within 1 and 100");
throw e;
throw new ArgumentOutOfRangeException
("Value must be within 1 and 100");
[edit]
Performance
As previously stated, exception handling comes with a performance penalty. Using aHRESULT
or a similar special return value is more efficient but inflexible when it comes to management, debugging, or reuse of the code. Exception handling in a managed runtime environment like the .NET or Java runtimes is more efficient that in an unmanaged environment like C++; because, the C++ Compiler has to write bookkeeping code (i.e. to track objects on the heap), whereas in a managed environment much of this bookkeeping code is implicit (i.e. garbage collection). The exact overhead for
Exception
handling in .NET is hard to predict, because it depends how the compiler on the target platform implements it. Some compilers are using lookup tables, others generate special methods for this task. When it comes to performance tests you should do them on the target platform. In Microsoft Windows, you can use
PerfMon.exe
, or the System Monitor ActiveX control. When using PerfMon
, you select .Net CLR Exceptions as Performance Object and then select the statistics which you are most interrested in. [edit]
Unhandled Exceptions
If a exception occurs the CLR traverses up the call stack looking for a matching catch expression. If the CLR doen't finds a matching one, or the Exception gets re thrown each time, the Exception bubbles out of theMain()
method. In that case Windows handles the Exception. First, Windows is searching for a fitting registered debugger like Visual Studio which can handle the Exception. Visual Studio has very powerfully mechanisms, so i.e.. for a debug-compiled version Visual Studio jumps directly to the place within the source where the Exception has occurred. Further Visual Studio 2005 provides mechanisms to look at the current values of all the Variables which provides an immense programming-advance compared to most other debuggers. Release compiled Version of your Program doesn't include the additional debug-information needed to map the code to the source. This leads to faster execution, but on the one hand you have to debug within IL. At best it is possible to find the right function within the source, based on the information within the exception (
Exception.StackTrace
). Also the mapping of the value of the variables gets lost if they are not stored within the exception too (remember Exception.ParamName
and Exception.ActualValue
). The debugger of Microsoft's Visual Studio 2005 offers special exception handling support. The Dialog providing the settings for this can be found in Debug/Exceptions from the Menu bar. A detailed description can be found within the documentation of Visual Studio.
[edit]
Logging Exceptions
You can log exceptions withSystem.Diagnostics.EventLog
if the user uses Windows 2k/XP/2003/Vista. A very good thing when working with public available versions of your program is to set up a server running a public available EventLog, or - on Unix Systems - a special developed WebService to which you can report. On uncritical Exceptions you can also present the user a special Window providing text-fields to give additional information (E-Mail Address for contact; Information about the user has done with your Application; etc. - but don't ask for things like the used operating system; figure it out by code where possible). At least it is a much smarter approach than providing just a E-Mail Address or a Webforum, as most developers do, because the error-report is more problem oriented and goes directly to the responsible programmer. If you want to do remote logging let the user decide if she really wants to send the report or not. - Important: It is not possible to create a new window when there is ie. a out-of-memory Exception. If you try it you will get a new out-of-memory Exception, trying to create a new window, creating a new Exception.... This can cause a hangup of the operating-system.
[edit]
Exception policies
It is also possible to register an Event Handler for unhandled exceptions. The code below gives an example, how this might look like. The Example works also within threads, so you don't really have to terminate the whole application which can give you the possibility to recover, make an failure report, and maybe re spawn the thread.using System;
using System.Diagnostics;
using System.Windows.Forms;
class Application
{
static void Main()
{
// Register an Event Handler which
// handles the unhandled Exception
AppDomain.CurrentDomain.UnhandledException +=
new UnhandledExceptionEventHandler
(OnUnhandledExceptionPolicy);
try
{
// Application code comes here
}
finally
{
// Finalization code comes here
}
}
static void OnUnhandledExceptionPolicy
(Object Sender, UnhandledExceptionEventArgs e)
{
String InformationForLogging;
Exception ex = e.ExceptionObject as Exception;
if (ex != null)
{
// The unhandled Exception is CLS compliant
// Extract the Information you want to know
InformationForLogging = ex.ToString();
}
else
{
// The unhandled Exception is not CLS compliant
// You can only handle this Exception as Object
InformationForLogging = String.Format(
"Type: {0}{2}String: {1}",
e.ExceptionObject.GetType(),
e.ExceptionObject.ToString(),
Environment.NewLine)
}
#if DEBUG
if (!e.IsTerminating)
{
// Exception occurred in a thread pool or finalizer thread
// Debugger launches only explicitly
} else
{
// Exception occurred in managed thread
// Debugger will also launch when not launched explicitly
}
// explicitly launch the debugger (Visual Studio)
Debugger.Launch();
#else
// This is a final release;
// logging is done to Syslog and over the Web if possible
// Debugger is mostly not available
if (!e.IsTerminating)
{
// Exception occurred in a thread pool or finalizer thread
// Application keeps open
}
else
{
// Exception occurred in managed thread
// Application is closing, so you should log now
}
#endif
}
}
[edit]
Controlling CLR unhandled Exception
behavior
When a managed thread throws a unhandled Exception, the CLR looks into the registry for the kind of debugging. The Registry Key for this is named DbgJITDebugLaunchSetting
and can be found in HKEY_LOCAL_MACHINE\Software\Microsoft\.NetFramework\
. Value | Behavior |
---|---|
0 | Displays a dialog asking the user for debugging.
|
1 | No dialog box is presented to the user. The UnhandledException gets thrown immediately. |
2 | No dialog box is presented to the user. The CLR starts the debugger. |
16 | in the .NET Framework version 2.0 only, the runtime presents the message box described above for processes that are interactive with the desktop. For processes that are not interactive, the runtime spawns the debugger listed in the DbgManagedDebugger registry key. This is the default setting for the .NET Framework version 2.0. Ref: http://msdn.microsoft.com/en-us/library/2ac5yxx6%28VS.80%29.aspx |
jitDebugging
within the applications .config file. [edit]
Unhandled exceptions in different application types
Where and how the output of an unhandledException
occurs depends on the application type. There are Console Applications (CUI), WinForms Applications (GUI; including DirectX), ASP.Net Applications, and ASP.Net WebServices. [edit]
Console
Event Handling of Console Applications is the easiest to understand, because there is no special Handling by the CLR. The Exception is leaving the Applications Thread if not caught. The CLR opens a window asking for debug or exit the application. If the user chooses to debug, the debugger starts. If the user chooses to close, the Application exits and the Exception is serialized and written to the console.see also: Controlling CLRs Unhandled Exception Behavior
[edit]
Windows Forms
In a Windows Forms application, theRun
-method of the System.Windows.Forms.Application
-Class runs a loop within a new thread. This loop dispatches messages of the type System.Windows.Forms.NativeWindow
which contains a try/catch block. Within the try-block of this, Microsoft Windows WndProc
-Method is called to create the Window. The catch block of a Window calls the virtual
OnThreadException
-Method of the window when a CLR-Excception occurs. In the case of a control this method calls the OnThreadException
-Method from the Application. By default this calls a Window giving the user the possibility to continue or quit the application. If the user continues the application will be in an undefinded state, so he should save his work when possible - at best in a new file to avoid corruption of the original file - and restart the application. It is possible to override this window by providing an event handler of type
System.Threading.ThreadExceptionEventHandler
and registering it for the ThreadException
-Event of the Application. Because WindowsForms handle only CLR-Exceptions you might want to create also another
ThreadExceptionEventHandler
for this and register it for the UnhandledException
-Event of the Application. [edit]
ASP.Net Web Forms
In ASP.Net Web Forms, ASP.Net itself catches most Exceptions using its own try-block.Therefore it is possible to register a callback method using the
Error
-Event of System.Web.UI.TemplateControl
. The TemplateControl
-Class itself is the base class of System.Web.UI.Page
and System.Web.UI.UserControl
, so each Page and Control exposes this Event too. On the other hand it is possible to receive notifications about unhandled exceptions for a page by registering a callback method. You can register this method Application wide within the
Error
-Property of the System.Web.Application
-Class. Also, ASP.Net offers options to create a dump about the Exception to a Webpage.
[edit]
ASP.Net Web services
Web services have also a easy way of handling Exceptions. ASP.Net catches the Exception and throws a newSystem.Web.Services.Protocols.SoapException
. This SoapException
is serialized to XML, representing a SOAP-Fault. This gets understood by other SOAP Clients independently of the Platform or the Programming Environment. If the client is a .NET Framework application the SOAP-Fault is deserialized into a SoapException and thrown at the place where the WebService-Call occurred. This Exception can then get handled like any other exception.
0 Comments