Exception handling is a crucial aspect of writing robust and fault-tolerant applications in C#. It ensures that your application can gracefully handle unexpected errors without crashing. In this blog, we’ll explore the fundamentals of exception handling in C#, best practices, and advanced techniques.
What is an Exception?
An exception is an unexpected event that occurs during the execution of a program, disrupting the normal flow. Exceptions can be caused by various reasons such as invalid user input, file not found, network failures, or division by zero.
The try-catch Block
C# provides the try-catch block to handle exceptions gracefully.
try
{
int result = 10 / 0; // This will throw a DivideByZeroException
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
Multiple Catch Blocks
You can handle different types of exceptions using multiple catch blocks.
try
{
string[] arr = new string[2];
Console.WriteLine(arr[5]); // This will throw an IndexOutOfRangeException
}
catch (IndexOutOfRangeException ex)
{
Console.WriteLine("Index out of range: " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("General exception: " + ex.Message);
}
The finally Block
The finally block is used to execute code regardless of whether an exception occurs or not. It is often used for cleanup operations like closing file streams or database connections.
StreamReader reader = null;
try
{
reader = new StreamReader("file.txt");
Console.WriteLine(reader.ReadToEnd());
}
catch (FileNotFoundException ex)
{
Console.WriteLine("File not found: " + ex.Message);
}
finally
{
reader?.Close(); // Ensures the file is closed
Console.WriteLine("Cleanup complete.");
}
Throwing Exceptions
You can throw exceptions using the throw keyword.
void ValidateAge(int age)
{
if (age < 18)
{
throw new ArgumentException("Age must be 18 or older.");
}
Console.WriteLine("Valid age.");
}
try
{
ValidateAge(16);
}
catch (ArgumentException ex)
{
Console.WriteLine("Exception: " + ex.Message);
}
Custom Exceptions
In some cases, you may need to create your own exception classes.
public class InvalidAgeException : Exception
{
public InvalidAgeException(string message) : base(message) { }
}
void CheckAge(int age)
{
if (age < 18)
{
throw new InvalidAgeException("Age is below the required limit.");
}
}
try
{
CheckAge(16);
}
catch (InvalidAgeException ex)
{
Console.WriteLine("Custom Exception: " + ex.Message);
}
Advanced Exception Handling Techniques
Using when with Catch Blocks
The when keyword allows you to filter exceptions based on conditions.
try
{
int value = int.Parse("abc");
}
catch (FormatException ex) when (ex.Message.Contains("Input string was not in a correct format"))
{
Console.WriteLine("Caught a format exception: " + ex.Message);
}
Exception Handling with Task and async/await
When working with asynchronous programming, handling exceptions properly is crucial.
async Task ReadFileAsync()
{
try
{
using StreamReader reader = new StreamReader("file.txt");
string content = await reader.ReadToEndAsync();
Console.WriteLine(content);
}
catch (IOException ex)
{
Console.WriteLine("I/O Exception: " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("Unexpected Error: " + ex.Message);
}
}
Global Exception Handling
For applications like ASP.NET Core or WPF, a global exception handler ensures unhandled exceptions do not crash the application.
ASP.NET Core Global Exception Handling
public class CustomExceptionMiddleware
{
private readonly RequestDelegate _next;
public CustomExceptionMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
await context.Response.WriteAsync($"An error occurred: {ex.Message}");
}
}
}
Best Practices for Exception Handling
- Use specific exception types – Avoid catching generic
Exceptionunless necessary. - Avoid suppressing exceptions – Logging or handling them properly prevents hidden issues.
- Use
finallyfor resource cleanup – Ensure proper disposal of resources like file streams and database connections. - Do not use exceptions for control flow – Exceptions should be used for unexpected errors, not for normal logic.
- Log exceptions – Use logging frameworks (like Serilog, NLog, or log4net) to track exceptions.
- Use
throwto preserve stack trace – Avoidthrow ex;, as it resets the stack trace.
catch (Exception ex)
{
Console.WriteLine("An error occurred.");
throw; // Preserves stack trace
}
Conclusion
Exception handling is an essential skill for C# developers. By following best practices and leveraging C#’s robust exception-handling features, you can build more reliable and maintainable applications. Advanced techniques such as global exception handling and async exception handling further improve resilience.
Have any questions or best practices to share? Let’s discuss in the comments!
Leave a comment