C# tutorials > Core C# Fundamentals > Exception Handling > What is the exception hierarchy in .NET?
What is the exception hierarchy in .NET?
In .NET, exceptions are organized in a hierarchical structure rooted in the System.Exception
class. Understanding this hierarchy is crucial for effective exception handling, allowing you to catch specific types of exceptions or handle broader categories based on your application's needs.
The Root: System.Exception
System.Exception
is the base class for all exceptions in .NET. It provides fundamental properties and methods for exception handling, such as Message
(a human-readable description of the error), StackTrace
(information about the call stack), and InnerException
(allowing you to chain exceptions).
Direct Descendants of System.Exception
Two key classes directly inherit from System.Exception
: System.SystemException
and System.ApplicationException
. However, Microsoft recommends not deriving custom exceptions directly from System.ApplicationException
. It was intended to be a base class for application-specific exceptions, but its use is now discouraged.System.SystemException
represents exceptions thrown by the Common Language Runtime (CLR). It serves as the base class for many runtime-related exceptions.
Common SystemException Derivatives
Several important exception types inherit from System.SystemException
. Here are a few frequently encountered ones:
System.ArgumentException
: Base class for exceptions related to invalid arguments passed to a method. Includes subclasses like ArgumentNullException
(when a null argument is unexpectedly passed) and ArgumentOutOfRangeException
(when an argument is outside the allowed range).System.InvalidOperationException
: Thrown when a method call is invalid in the object's current state.System.NotSupportedException
: Indicates that a requested method or operation is not supported.System.FormatException
: Thrown when the format of an argument is invalid.System.NullReferenceException
: Thrown when attempting to dereference a null object reference. This is a very common exception.System.IndexOutOfRangeException
: Thrown when attempting to access an array element with an index that is outside the bounds of the array.System.StackOverflowException
: Thrown when the execution stack overflows, typically due to infinite recursion. This exception cannot usually be handled by user code; the CLR will usually terminate the process.System.OutOfMemoryException
: Thrown when the CLR cannot allocate sufficient memory to continue execution. This exception also cannot usually be handled by user code and typically leads to process termination.
Creating Custom Exceptions
To create custom exceptions, inherit directly from The example shows how to create a custom System.Exception
. Provide constructors that allow setting the message and an optional inner exception.InsufficientFundsException
for a banking application. It includes properties for the current balance and the attempted withdrawal amount, allowing the exception handler to access these values.
public class InsufficientFundsException : Exception
{
public decimal Balance { get; }
public decimal WithdrawalAmount { get; }
public InsufficientFundsException(string message, decimal balance, decimal withdrawalAmount) : base(message)
{
Balance = balance;
WithdrawalAmount = withdrawalAmount;
}
public InsufficientFundsException(string message, decimal balance, decimal withdrawalAmount, Exception innerException) : base(message, innerException)
{
Balance = balance;
WithdrawalAmount = withdrawalAmount;
}
}
Catching Exceptions
When catching exceptions, it's generally best to catch specific exception types first and then a more general type like Catching Exception
at the end. This allows you to handle different errors in different ways. The example shows catching a DivideByZeroException
, and then a generic Exception
for any other errors that might occur.Exception
as the last catch block ensures that no exception goes unhandled, which can prevent the application from crashing. It is a good practice to log unhandled exceptions for debugging and analysis.
try
{
// Code that might throw an exception
int result = 10 / 0; // Example: Division by zero
}
catch (DivideByZeroException ex)
{
// Handle the specific exception
Console.WriteLine("Error: Division by zero.");
Console.WriteLine(ex.Message);
}
catch (Exception ex)
{
// Handle any other exception
Console.WriteLine("An unexpected error occurred.");
Console.WriteLine(ex.Message);
}
Real-Life Use Case: File I/O
When working with file I/O, several exceptions can occur, such as FileNotFoundException
, IOException
, and SecurityException
. Catching these specific exceptions allows you to provide more informative error messages to the user and handle the errors appropriately. For example, if a file is not found, you could prompt the user to enter a different file name.
try
{
string fileContent = File.ReadAllText("nonexistent_file.txt");
}
catch (FileNotFoundException ex)
{
Console.WriteLine("Error: The file was not found.");
}
catch (IOException ex)
{
Console.WriteLine("Error: An I/O error occurred while reading the file.");
}
catch (Exception ex)
{
Console.WriteLine("An unexpected error occurred: " + ex.Message);
}
Best Practices
Exception
early in the try/catch block as it can mask more specific exceptions.throw;
to preserve the original stack trace.finally
blocks to ensure that resources are cleaned up, even if an exception occurs.
Interview Tip
When discussing exception handling in interviews, emphasize your understanding of the exception hierarchy and your ability to choose the appropriate exception types to catch and handle. Be prepared to discuss custom exception creation and best practices for exception management.
When to use them
Use specific exception types when you need to handle particular error conditions differently. Catching general exceptions is suitable when you need to perform generic error handling or logging without specific logic for individual error types.
FAQ
-
Why shouldn't I derive directly from `ApplicationException`?
Microsoft's guidelines advise against it. Its original purpose has been superseded, and deriving from
System.Exception
provides sufficient functionality for custom exceptions. -
What is the purpose of an inner exception?
An inner exception allows you to chain exceptions together, providing more detailed information about the cause of an error. It's useful when an exception handler catches an exception and then throws a new exception that provides more context.
-
When should I rethrow an exception?
Rethrow an exception when you catch it, perform some action (e.g., logging), but cannot fully handle the error. This allows a higher level of the application to address the issue. Always use
throw;
to preserve the original stack trace.