Exception handling is the black art of doing something to repair an unpredicted error or malfunction. Within CLR, anytime something happens outside our prevision, such as setting an Int16
typed variable with a value outside valid ranges, the CLR will handle such an event by itself, creating an instance of an Exception
class and breaking the execution of our code, trying instead to find some other code able to handle (a.k.a catch
) such an exception.
Any Exception
class is populated with all useful details regarding what just happened, like a simplified error text (within the Message
property), the StackTrace
that explains exactly the whole method call hierarchy, and other details. Often, instead of a simple Exception
class, an inheritance child is instantiated to collect specific additional details or simply to define the kind of exception just raised. Indeed, setting an outranged value within an Int16
typed variable will raise an OverflowException
event in place of a simple Exception
event.
As just said, an exception is usually handled within a .NET-based application in response to an unpredicted error, although an exception is actually a special GoTo
statement that will alter the control-flow of our application. This is the why its called as an exception instead of an error.
Anytime an error happens, or simply when the flow cannot proceed as normal, an exception is created and raised (raising an exception actually starts the control-flow alteration) to avoid completing a method run, maybe, because its data is inconsistent. We can also create our exceptions regarding our business, components, or helpers/frameworks if special parameters are needed to flow.
Carefully create and handle exceptions because of the cost the CLR incurs when any exception is raised. Although an exception will start a control-flow alteration, at the beginning, CLR will compute the full call stack of the executing thread. This is a CPU-intensive operation that actually stops the thread from running any other code. This is proof that Microsoft considers the entire exception-handling framework as error management. Therefore, it is impossible to create a multiple control-flow application using exception handling without enabling great wastage of resources. This means that, if we still need to be creating multiple control-flow applications, we will still need to use the goto
keyword.
Here is a graphical representation that shows the control-alteration made by any exception:
As seen in the preceding diagram, when the control-flow changes, the CLR searches for some catch
code-block. This may be locally, or at any calling level, up to the program's Main
method. Here is a classical implementation in C#:
int a = 0; try { //normal control-flow a = 10; a = a / int.Parse("0"); //this will raise a DivideByZeroException } catch (DivideByZeroException dx) { //altered control-flow if CLR raises DivideByZeroException Console.WriteLine(dx.Message); } catch (Exception ex) { //altered control-flow if CLR raises any other exception Console.WriteLine(ex.Message); } finally { //usually used for cleanup resources //or restore data state a = 0; }
The CLR executes the code within the try
block; then, when an exception is raised, CLR searches the best-fit altered control-flow by matching handled exceptions (with the catch
keyword) with the exception raised. The search is from top to bottom and supports class hierarchy. This means that the less specific exception (the one handled with the generic Exception
class) must be always the last one. Otherwise, other exceptions will never be matched.
In the preceding example, there is a catch
block for such an exact exception raised, so the flow will continue in that block. After any try
or catch
block, the finally
block is invoked, if present (optional).
A try-catch block can be nested in others if needed, although this may lead to a control flow that is tricky to understand with all those alterations. By nesting exceptions, CLR still goes in search of a catch
block from the deeper code row where the exception has originated, flowing up to the process' Main
method. Such exceptions lift, like an air bubble in the water, it lets programmers work with exception-handing search as made by CLR from the deeper code to the most outer one (the Main
method), the name of exception bubbling.
Raising new exceptions is actually simple; it is enough to use the throw
keyword and pass the new exception:
throw new Exception("HI");
Any time an exception is raised somewhere, the related AppPool
is notified of the FirstChanceException
event that actually runs before the bubbling occurs—in other words, at the start of the bubbling.
During the bubbling, if the CLR cannot find any valid catch
block, the related AppDomain (the one where the exception originated) is notified on the UnhandledException
event. Although this cannot handle the exception as a super catch
block can, it can somehow notify application users or the system administrator gracefully before the critical exit of the process. Here is an such an example:
static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { //contains exception details var ex = e.ExceptionObject; //if true the process will terminate var willKillCLR = e.IsTerminating; } static void CurrentDomain_FirstChanceException(object sender, FirstChanceExceptionEventArgs e) { //contains exception details var ex = e.Exception; }
After the UnhandledException
event occurs, the AppDomain
class is unloaded by CLR.
A special case occurs when another compiler raises a non Common Language Specification (CLS) compatible exception (such as raising a string
exception or int
exception—classes that do not inherit from the Exception
class). Although this is a rare opportunity, some external vendor language implementation could work with such behavior. In this case, the CLR will raise a RuntimeWrappedException
event with the ability to read raw exception data, such as an int
value or a string
value, as an internal exception.
Another special case to be aware of is that a finally
block—although it is usually called, anything that can happen within a catch
block—cannot run if the executing thread is killed by unmanaged code, by invoking the Win32 KillThread
or KillProcess
method.
Obviously, in such cases, Windows will also kill the whole process with anything within. The only leak that will still survive will occur when you launch an external process driven by your application that somehow was killed by Windows. In this case, the finally block is avoided and the external process can remain in memory. A widely-used solution is to always check if zombie external processes are still alive from previous executions of the application, when we start a new instance of such an application.
3.142.40.32