Skip to main content

C# Multiple Catches

Introduction

When writing robust C# applications, effectively handling exceptions is crucial to prevent unexpected crashes. While a single catch block can handle all exceptions, using multiple catch blocks allows for more specific error handling based on the type of exception that occurs. This approach enables you to respond appropriately to different error conditions, making your code more resilient and user-friendly.

In this tutorial, you'll learn how to implement and use multiple catch blocks in C# to handle different types of exceptions with tailored responses.

Understanding Multiple Catch Blocks

In C#, a try block can be followed by multiple catch blocks, each designed to catch a specific type of exception. When an exception occurs, the runtime looks for a matching catch block in the order they're defined.

Here's the basic syntax:

csharp
try
{
// Code that might throw exceptions
}
catch (SpecificExceptionType1 ex1)
{
// Handle SpecificExceptionType1
}
catch (SpecificExceptionType2 ex2)
{
// Handle SpecificExceptionType2
}
catch (Exception ex)
{
// Handle any other exceptions
}
finally
{
// Clean-up code that runs regardless of whether an exception occurred
}

Basic Example of Multiple Catches

Let's start with a simple example that demonstrates how to use multiple catch blocks to handle different types of exceptions:

csharp
using System;

class Program
{
static void Main()
{
Console.WriteLine("Enter a number:");

try
{
string input = Console.ReadLine();
int number = int.Parse(input);

Console.WriteLine($"10 divided by {number} is: {10 / number}");
}
catch (FormatException ex)
{
Console.WriteLine($"Error: You didn't enter a valid number. ({ex.Message})");
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"Error: You cannot divide by zero. ({ex.Message})");
}
catch (Exception ex)
{
Console.WriteLine($"An unexpected error occurred: {ex.Message}");
}

Console.WriteLine("Program continues execution...");
}
}

Example Output:

If the user enters "0":

Enter a number:
0
Error: You cannot divide by zero. (Attempted to divide by zero.)
Program continues execution...

If the user enters "abc":

Enter a number:
abc
Error: You didn't enter a valid number. (Input string was not in a correct format.)
Program continues execution...

If the user enters "5":

Enter a number:
5
10 divided by 5 is: 2
Program continues execution...

Order of Catch Blocks Matters

When using multiple catch blocks, it's important to arrange them from most specific to most general. C# evaluates catch blocks in order, and once it finds a matching exception type, it executes that block and skips the rest.

Let's look at an example with incorrect and correct ordering:

Incorrect Order (Won't Compile)

csharp
try
{
// Some code that might throw exceptions
}
catch (Exception ex) // General exception first
{
Console.WriteLine($"General exception: {ex.Message}");
}
catch (DivideByZeroException ex) // More specific exception after general
{
Console.WriteLine($"Divide by zero: {ex.Message}");
}
// This will cause a compilation error because DivideByZeroException
// will never be caught (Exception already catches everything)

Correct Order

csharp
try
{
// Some code that might throw exceptions
}
catch (DivideByZeroException ex) // More specific exception first
{
Console.WriteLine($"Divide by zero: {ex.Message}");
}
catch (Exception ex) // General exception last
{
Console.WriteLine($"General exception: {ex.Message}");
}

Exception Filters with when Clause

C# 6.0 introduced exception filters, which allow you to add conditions to catch blocks using the when keyword. This further refines which exceptions a particular catch block will handle.

csharp
using System;
using System.IO;

class Program
{
static void Main()
{
try
{
string content = File.ReadAllText("data.txt");
Console.WriteLine(content);
}
catch (FileNotFoundException ex) when (ex.FileName.Contains("data"))
{
Console.WriteLine($"The data file was not found: {ex.FileName}");
}
catch (FileNotFoundException ex)
{
Console.WriteLine($"A different file was not found: {ex.FileName}");
}
catch (IOException ex)
{
Console.WriteLine($"An I/O error occurred: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"An unexpected error occurred: {ex.Message}");
}
}
}

In this example, we use the when clause to create a specific handler for data files that are missing, separate from other missing files.

Practical Example: File Processing Application

Let's look at a more real-world example of using multiple catch blocks in a file processing application:

csharp
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;

class FileProcessor
{
public void ProcessNetworkFile(string url, string localPath)
{
Console.WriteLine($"Attempting to process {url} and save to {localPath}");

try
{
// Download file from network
using (WebClient client = new WebClient())
{
client.DownloadFile(url, localPath);
}

// Read and process the file
string content = File.ReadAllText(localPath);
int charCount = content.Length;
Console.WriteLine($"File processed successfully! Character count: {charCount}");
}
catch (WebException ex) when (ex.Status == WebExceptionStatus.NameResolutionFailure)
{
Console.WriteLine($"Network error: Could not resolve the hostname. Check your internet connection.");
LogException(ex);
}
catch (WebException ex) when (ex.Status == WebExceptionStatus.Timeout)
{
Console.WriteLine($"Network error: The request timed out. The server might be busy.");
LogException(ex);
}
catch (WebException ex)
{
Console.WriteLine($"Network error: {ex.Message}");
LogException(ex);
}
catch (DirectoryNotFoundException ex)
{
Console.WriteLine($"File system error: The directory for saving the file does not exist.");
LogException(ex);
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine($"Security error: You don't have permission to save the file to {localPath}");
LogException(ex);
}
catch (IOException ex)
{
Console.WriteLine($"I/O error: {ex.Message}");
LogException(ex);
}
catch (Exception ex)
{
Console.WriteLine($"Unexpected error: {ex.Message}");
LogException(ex);
}
finally
{
Console.WriteLine("File processing attempt completed.");
}
}

private void LogException(Exception ex)
{
// In a real application, this would write to a log file or logging service
Console.WriteLine($"[LOG] {DateTime.Now}: {ex.GetType().Name} - {ex.Message}");
Console.WriteLine($"[LOG] Stack trace: {ex.StackTrace}");
}

public static void Main()
{
FileProcessor processor = new FileProcessor();
processor.ProcessNetworkFile("https://example.com/nonexistent-file.txt", @"C:\temp\output.txt");
}
}

This example demonstrates how to handle different types of exceptions that might occur during file operations:

  • Network-related exceptions with filtering by status
  • File system exceptions
  • Security exceptions
  • General I/O exceptions
  • And a catch-all for any unexpected exceptions

Best Practices for Using Multiple Catch Blocks

  1. Order from specific to general: Always arrange catch blocks from most specific to most general exception types.

  2. Only catch exceptions you can handle: Don't catch exceptions just to hide them. Only catch exceptions you can meaningfully respond to.

  3. Keep catch blocks focused: Each catch block should handle only one specific type of error condition.

  4. Use exception filters for complex conditions: Use the when clause for conditional exception handling instead of putting if statements inside catch blocks.

  5. Log exceptions properly: Even when catching and handling exceptions, make sure to log them for debugging purposes.

  6. Don't swallow exceptions silently: Always provide feedback to users or log information when exceptions occur.

  7. Use the finally block for cleanup: Put cleanup code in a finally block to ensure it runs regardless of whether an exception occurred.

Putting It All Together: A Complete Example

Here's a comprehensive example that illustrates all the concepts we've covered:

csharp
using System;
using System.IO;

class MultipleExceptionHandlingDemo
{
public static void Main()
{
Console.WriteLine("Multiple Exception Handling Demo");
Console.WriteLine("================================");

string filePath = "data.txt";
string backupPath = "data_backup.txt";

try
{
ProcessFile(filePath, backupPath);
}
catch (Exception ex)
{
Console.WriteLine($"Main method caught: {ex.GetType().Name}");
}

Console.WriteLine("Program completed.");
}

static void ProcessFile(string filePath, string backupPath)
{
try
{
Console.WriteLine($"Attempting to process file: {filePath}");

// Check if file exists
if (!File.Exists(filePath))
{
throw new FileNotFoundException($"The file {filePath} was not found.", filePath);
}

// Read file content
string[] lines = File.ReadAllLines(filePath);

// Process each line (could throw FormatException)
ProcessLines(lines);

// Make a backup
File.Copy(filePath, backupPath, true);

Console.WriteLine("File processed successfully.");
}
catch (FileNotFoundException ex) when (ex.FileName == filePath)
{
Console.WriteLine($"Error: Source file not found. Creating an empty file.");
File.WriteAllText(filePath, "This is a new file.");
}
catch (FileNotFoundException ex)
{
Console.WriteLine($"Error: Some other file was not found: {ex.FileName}");
}
catch (FormatException ex)
{
Console.WriteLine($"Error: Invalid data format in the file. {ex.Message}");
Console.WriteLine("Attempting to process the file in raw mode...");
try
{
string content = File.ReadAllText(filePath);
Console.WriteLine($"Raw content length: {content.Length} characters");
}
catch (Exception nestedEx)
{
Console.WriteLine($"Raw processing failed: {nestedEx.Message}");
}
}
catch (IOException ex)
{
Console.WriteLine($"I/O Error: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"Unexpected error: {ex.Message}");
throw; // Re-throw the exception to be caught by the calling method
}
finally
{
Console.WriteLine("File processing completed (whether successful or not).");
}
}

static void ProcessLines(string[] lines)
{
for (int i = 0; i < lines.Length; i++)
{
try
{
Console.WriteLine($"Processing line {i + 1}");
string line = lines[i];

// Example processing: Parse line as an integer
int value = int.Parse(line);
Console.WriteLine($"Parsed value: {value}");
}
catch (FormatException)
{
Console.WriteLine($"Warning: Line {i + 1} is not a valid integer.");
}
}
}
}

Summary

Multiple catch blocks in C# provide a powerful way to handle different types of exceptions with specific responses. By organizing catch blocks from most specific to most general, you can create more robust and user-friendly applications.

Key points covered in this tutorial:

  • Basic syntax and usage of multiple catch blocks
  • The importance of catch block ordering
  • Using exception filters with the when clause
  • Best practices for exception handling
  • Real-world examples of multiple catch blocks in action

By implementing these techniques in your C# applications, you'll be able to gracefully handle errors and provide better feedback to users, resulting in more professional and reliable software.

Additional Resources

Exercises

  1. Create a calculator program that handles different exceptions (DivideByZeroException, FormatException, OverflowException) with appropriate messages.

  2. Modify the file processing example to include additional exception types like UnauthorizedAccessException and PathTooLongException.

  3. Create a method that reads and writes to a database, handling SqlException with different catch blocks based on the error number.

  4. Implement a web client that downloads files and uses exception filters to handle different network error conditions.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)