Exception Handling in C#
- Understanding Exceptions
Exception handling aims to respond to exceptions by performing one or more of the following actions: correction, logging, external resource cleanup, or providing user-friendly messages.
- The try Block
The try block contains code segments protected against exceptions, with handlers provided for when they occur. A catch block contains one or more handlers for exceptions. The finally block includes code that must execute under all circumstances.
- Exception Classes
The Base Class Library defines numerous exception classes, each representing a specific type of exception. When an exception occurs, the CLR creates an object of that exception type and searches for an appropriate catch clause. All exceptions derive from System.Exception.
Exception objects contain read-only properties that provide details about the error.
| Property | Type | Description |
|---|---|---|
| Message | string | Contains a message explaining the cause of the exception. |
| StackTrace | string | Describes where the exception occurred. |
| InnerException | Exception | If the current exception was caused by another, this holds a reference to the previous exception. |
| Source | string | If not set by the application, contains the name of the assembly where the exception originated. |
- catch Clauses
There are four forms of catch clauses, allowing differant levels of handling:
4.1 General catch
Matches any exception type thrown within the try block. It can handle any exception but cannot determine the specific exception type.
catch
{
// Statements
}
4.2 Specific catch
Matches exceptions of a specified class or its derived classes.
catch(ExceptionType)
{
// Statements
}
4.3 Specific catch with object
Includes an identifier after the exception class name, creating an exception variable that can be accessed within the catch block to obtain detailed information about the exception.
catch(ExceptionType ExceptionVar)
{
// Statements
}
4.4 Specific catch with predicate
Execution enters this clause only when the predicate evaluates to true.
catch(ExceptionType ExceptionVar) when (predicate)
{
// Statements
}
Example
int numerator = 10;
try
{
int denominator = 0;
int result = numerator / denominator; // Throws exception
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"1. {ex.Message}");
Console.WriteLine($"2. {ex.Source}");
Console.WriteLine($"3. {ex.StackTrace}");
}
- Exception Filters
A single exception type can have multiple handlers. The exception object is past to the handler when the catch clause's conditions are satisfied.
try
{
// Code that may throw exceptions
}
catch (HttpRequestException ex) when (ex.Message.Contains("307"))
{
// Handle 307 redirect
}
catch (HttpRequestException ex) when (ex.Message.Contains("301"))
{
// Handle 301 redirect
}
Key properties of the when clause:
- Must contain a predicate expression returning true or false.
- Cannot be asynchronous.
- Should not involve long-running operations.
- Exceptions within the predicate expression are ignored while preserving debugging information.
- catch Clause Section
If a catch clause accepts a parameter, the system sets this exception variable to reference the exception object, anabling inspection to determine the cause. If the exception resulted from a previous exception, the InnerException property provides access to it.
Multiple catch clauses are allowed, but only one general catch clause is permitted. When an exception occurs, the system searches the catch clauses in order; the first matching clause executes. Therefore, two important rules apply:
-
Specific catch clauses must be ordered from most specific to most general (subclass exceptions before parent class exceptions).
-
If a general catch clause exists, it must be last, following all specific catch clauses. Its use is discouraged as it may allow program continuation while hiding errors.
-
finally Block
If no exception occurs within the try block, control flows to the finally block after the try block ends. If an exception occurs, the appropriate catch clause executes, followed by the finally block.
- Locating Exception Handlers
When an exception occurs within a try block, the system checks for a matching catch clause. If found, one of three actions occurs:
-
The catch clause executes.
-
If a finally block exists, it executes.
-
Execution continues after the try statement (after the finally block if present, otherwise after the last catch clause).
-
Further Search
If an exception is thrown in code not protected by try or without a matching handler, the system searches the call stack sequentially for an enclosing try block with a matching handler.
Example
static void Main(string[] args)
{
Processor processor = new Processor();
try
{
processor.MethodA();
}
catch (DivideByZeroException)
{
Console.WriteLine("catch in Main");
}
finally
{
Console.WriteLine("finally in Main");
}
Console.WriteLine("Main continues");
}
class Processor
{
public void MethodA()
{
try
{
MethodB();
}
catch(IndexOutOfRangeException)
{
Console.WriteLine("catch in MethodA");
}
finally
{
Console.WriteLine("finally in MethodA");
}
}
void MethodB()
{
int value = 10;
int divisor = 0;
try
{
int quotient = value / divisor;
}
catch(IndexOutOfRangeException)
{
Console.WriteLine("catch in MethodB");
}
finally
{
Console.WriteLine("finally in MethodB");
}
}
}
- Throwing Exceptions
throw new ExceptionType();
- Throwing Without Exception Object - Re-throwing
The throw statement can be used without an exception object inside a catch block. This form re-throws the current exception, causing the system to continue searching for another handler. This form is only valid inside catch statements.
public static void DisplayArgument(string arg)
{
try
{
try
{
if(arg == null)
{
ArgumentException invalidArg = new ArgumentException();
throw invalidArg;
}
Console.WriteLine(arg);
}
catch (ArgumentException ex)
{
Console.WriteLine($"{ex.Message}");
throw; // Re-throw without parameter
}
}
catch
{
Console.WriteLine("outer catch handles Exception");
}
}
- throw Expressions
Throw expressions can be used where expressions are permitted. They function similarly to throw statements but as expressions.
Using throw with Null-Coalescing Operator
The null-coalescing operator (??) returns the first operand if not null, otherwise the second.
private int securityIdentifier;
public int SecurityIdentifier
{
get => securityIdentifier;
set => securityIdentifier = value ?? throw new ArgumentNullException("Security identifier cannot be null");
}
Using throw in Ternary Expressions
class SecurityManager
{
public static string AccessCode { get { return "Confidential123"; } }
static void Main()
{
bool isSecure = false;
try
{
string code = isSecure ? AccessCode : throw new Exception("Security check failed");
Console.WriteLine($"Code: {code}");
}
catch (Exception ex)
{
Console.WriteLine($"{ex.Message}");
}
}
}