.NET Exception Types
Introduction
Exception handling is a critical aspect of building robust applications in .NET. When something goes wrong during program execution, .NET uses exceptions to signal these error conditions. Understanding the different types of exceptions that can occur in a .NET application is essential for effective error handling and debugging.
In this tutorial, we'll explore the hierarchy of exception types in .NET, learn about common exceptions you'll encounter, and see how to work with each type through practical examples.
The .NET Exception Hierarchy
In the .NET Framework, all exceptions derive from the System.Exception
base class. This creates a hierarchical structure that allows for specific error handling based on the exception type.
Here's a simplified view of the exception hierarchy:
System.Object
└── System.Exception
├── System.SystemException
│ ├── System.ArgumentException
│ ├── System.ArithmeticException
│ ├── System.NullReferenceException
│ ├── System.InvalidOperationException
│ └── ...
└── System.ApplicationException
└── (Custom exceptions)
Let's examine some of the most important exception types you'll encounter.
Common Exception Types
System.Exception
This is the base class for all exceptions in .NET. It provides properties like Message
, StackTrace
, and InnerException
that are available to all derived exception types.
try
{
throw new Exception("This is a generic exception");
}
catch (Exception ex)
{
Console.WriteLine($"Exception Message: {ex.Message}");
Console.WriteLine($"Stack Trace: {ex.StackTrace}");
}
Output:
Exception Message: This is a generic exception
Stack Trace: at Program.<Main>$(String[] args) in C:\Projects\ConsoleApp1\Program.cs:line 7
System.ArgumentException & Related Exceptions
These exceptions occur when a method is called with invalid arguments.
ArgumentException
Thrown when one of the arguments provided to a method is invalid.
public void ProcessName(string name)
{
if (name.Contains(" "))
{
throw new ArgumentException("Name cannot contain spaces", nameof(name));
}
Console.WriteLine($"Processing name: {name}");
}
// Usage example
try
{
ProcessName("John Doe");
}
catch (ArgumentException ex)
{
Console.WriteLine($"Error: {ex.Message}");
Console.WriteLine($"Parameter name: {ex.ParamName}");
}
Output:
Error: Name cannot contain spaces
Parameter name: name
ArgumentNullException
A more specific version of ArgumentException that's thrown when a null reference is passed to a method that doesn't accept it.
public void ProcessOrder(Order order)
{
if (order == null)
{
throw new ArgumentNullException(nameof(order), "Order cannot be null");
}
// Process the order...
}
// Usage example
try
{
ProcessOrder(null);
}
catch (ArgumentNullException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
Output:
Error: Order cannot be null (Parameter 'order')
ArgumentOutOfRangeException
Thrown when the value of an argument is outside the allowable range.
public void SetAge(int age)
{
if (age < 0 || age > 120)
{
throw new ArgumentOutOfRangeException(nameof(age), age, "Age must be between 0 and 120");
}
Console.WriteLine($"Age set to: {age}");
}
// Usage example
try
{
SetAge(150);
}
catch (ArgumentOutOfRangeException ex)
{
Console.WriteLine($"Error: {ex.Message}");
Console.WriteLine($"Actual value was: {ex.ActualValue}");
}
Output:
Error: Age must be between 0 and 120 (Parameter 'age')
Actual value was: 150
System.NullReferenceException
This exception occurs when you try to access a member on a reference type variable that points to null.
try
{
string name = null;
int length = name.Length; // This will throw NullReferenceException
}
catch (NullReferenceException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
Output:
Error: Object reference not set to an instance of an object.
System.InvalidOperationException
Thrown when a method call is invalid for the object's current state.
public class BankAccount
{
public decimal Balance { get; private set; }
public bool IsLocked { get; private set; }
public void Deposit(decimal amount)
{
if (IsLocked)
{
throw new InvalidOperationException("Cannot deposit to a locked account");
}
Balance += amount;
}
public void Lock()
{
IsLocked = true;
}
}
// Usage example
try
{
var account = new BankAccount();
account.Lock();
account.Deposit(100);
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"Operation failed: {ex.Message}");
}
Output:
Operation failed: Cannot deposit to a locked account
System.IndexOutOfRangeException
Thrown when attempting to access an element of an array or collection with an index that is outside its bounds.
try
{
int[] numbers = { 1, 2, 3, 4, 5 };
int value = numbers[10]; // This will throw IndexOutOfRangeException
}
catch (IndexOutOfRangeException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
Output:
Error: Index was outside the bounds of the array.
System.IO.IOException
This is the base class for exceptions that are thrown when an I/O error occurs.
try
{
// Attempt to read from a file that doesn't exist
string content = File.ReadAllText("nonexistent-file.txt");
}
catch (FileNotFoundException ex)
{
Console.WriteLine($"File error: {ex.Message}");
Console.WriteLine($"File path: {ex.FileName}");
}
catch (IOException ex)
{
Console.WriteLine($"I/O error: {ex.Message}");
}
Output:
File error: Could not find file 'C:\Projects\ConsoleApp1\bin\Debug\net6.0\nonexistent-file.txt'.
File path: C:\Projects\ConsoleApp1\bin\Debug\net6.0\nonexistent-file.txt
System.FormatException
Thrown when the format of an argument does not meet the parameter specifications of the invoked method.
try
{
string input = "not-a-number";
int number = int.Parse(input); // This will throw FormatException
}
catch (FormatException ex)
{
Console.WriteLine($"Format error: {ex.Message}");
}
Output:
Format error: The input string 'not-a-number' was not in a correct format.
Creating Custom Exception Types
In addition to the built-in exceptions, you can create your own custom exception types when the standard exceptions don't adequately describe your specific error conditions.
Here's how to create a custom exception:
public class InsufficientFundsException : Exception
{
public decimal CurrentBalance { get; }
public decimal WithdrawalAmount { get; }
public InsufficientFundsException(string message, decimal currentBalance, decimal withdrawalAmount)
: base(message)
{
CurrentBalance = currentBalance;
WithdrawalAmount = withdrawalAmount;
}
public InsufficientFundsException(string message, decimal currentBalance, decimal withdrawalAmount, Exception innerException)
: base(message, innerException)
{
CurrentBalance = currentBalance;
WithdrawalAmount = withdrawalAmount;
}
}
// Usage example
public class BankAccount
{
public decimal Balance { get; private set; }
public BankAccount(decimal initialBalance)
{
Balance = initialBalance;
}
public void Withdraw(decimal amount)
{
if (amount > Balance)
{
throw new InsufficientFundsException(
"Insufficient funds for this withdrawal",
Balance,
amount);
}
Balance -= amount;
Console.WriteLine($"Withdrew {amount:C}. New balance: {Balance:C}");
}
}
// Client code
try
{
var account = new BankAccount(100);
account.Withdraw(200); // This will throw our custom exception
}
catch (InsufficientFundsException ex)
{
Console.WriteLine($"Error: {ex.Message}");
Console.WriteLine($"Current balance: {ex.CurrentBalance:C}");
Console.WriteLine($"Attempted withdrawal: {ex.WithdrawalAmount:C}");
}
Output:
Error: Insufficient funds for this withdrawal
Current balance: $100.00
Attempted withdrawal: $200.00
Best Practices for Exception Types
-
Choose the right exception type: Use the most specific exception type that accurately describes the error condition.
-
Preserve the original exception: When catching and re-throwing exceptions, use the inner exception parameter to preserve the original stack trace.
try
{
// Code that might throw an exception
int.Parse("not-a-number");
}
catch (FormatException ex)
{
// Wrap the original exception with additional context
throw new ApplicationException("Error processing user input", ex);
}
-
Don't use exceptions for normal flow control: Exceptions should be used for exceptional circumstances, not as a way to control normal program flow.
-
Document the exceptions your methods can throw: Use XML documentation to clearly indicate which exceptions your methods can throw.
/// <summary>
/// Withdraws money from the account.
/// </summary>
/// <param name="amount">The amount to withdraw.</param>
/// <exception cref="ArgumentException">Thrown when amount is negative.</exception>
/// <exception cref="InsufficientFundsException">Thrown when there are insufficient funds.</exception>
public void Withdraw(decimal amount)
{
// Implementation...
}
Real-World Example: A File Processing Utility
Let's put our knowledge of exception types together in a simple file processing utility:
public class FileProcessor
{
public void ProcessFile(string filePath, string outputPath)
{
// Input validation
if (string.IsNullOrEmpty(filePath))
throw new ArgumentNullException(nameof(filePath));
if (string.IsNullOrEmpty(outputPath))
throw new ArgumentNullException(nameof(outputPath));
if (!File.Exists(filePath))
throw new FileNotFoundException("The specified input file was not found.", filePath);
try
{
// Read the file content
string content = File.ReadAllText(filePath);
// Process the content (e.g., convert to uppercase)
string processedContent = content.ToUpper();
// Write the processed content to the output file
File.WriteAllText(outputPath, processedContent);
Console.WriteLine($"File processed successfully. Output: {outputPath}");
}
catch (IOException ex)
{
throw new ApplicationException("An error occurred during file I/O operations.", ex);
}
catch (UnauthorizedAccessException ex)
{
throw new ApplicationException("Access to the file was denied.", ex);
}
}
}
// Usage
try
{
var processor = new FileProcessor();
processor.ProcessFile("input.txt", "output.txt");
}
catch (ArgumentNullException ex)
{
Console.WriteLine($"Invalid argument: {ex.ParamName} cannot be null.");
}
catch (FileNotFoundException ex)
{
Console.WriteLine($"File not found: {ex.FileName}");
}
catch (ApplicationException ex)
{
Console.WriteLine($"Application error: {ex.Message}");
if (ex.InnerException != null)
{
Console.WriteLine($"Caused by: {ex.InnerException.Message}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Unexpected error: {ex.Message}");
}
This example demonstrates how different exception types are used to handle various error conditions that might occur during file processing.
Summary
Understanding .NET exception types is crucial for writing robust applications. In this guide, we've covered:
- The exception hierarchy in .NET
- Common built-in exception types and when to use them
- How to create custom exception types
- Best practices for working with exceptions
- A real-world example of exception handling in a file processing utility
By using the right exception types and handling them appropriately, you can create more robust and maintainable applications that gracefully handle error conditions.
Additional Resources
- Microsoft Docs: Exception Class
- Microsoft Docs: Exception Handling Best Practices
- C# Exception Handling Documentation
Exercise
-
Create a
Calculator
class that performs basic arithmetic operations (add, subtract, multiply, divide). Implement proper exception handling for each operation. -
Create a custom exception class called
NegativeResultException
that is thrown when an operation would result in a negative number. -
Write a program that reads user input as numbers and performs calculations. Handle all possible exceptions that could occur (like invalid input, division by zero, etc.).
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)