Mastering C# Part 6 - Exception Handling
Everything about Exceptions in C#

Hi, I'm Nishant Banjade, a Software Engineer at Ellucian, committed to transforming education through technology. With expertise in developing CRM systems, plugins, workflows, APIs, and customized solutions for Higher Education, I bring a strong focus on innovation and efficiency. Currently, I'm honing my skills in full-stack software development, system design, data structures, algorithms, and Microsoft Dynamics 365.
I am a skilled software developer with expertise in C#/.NET, D365, JavaScript, TypeScript, ReactJS, SQL, and cloud technologies like AWS, along with experience in RabbitMQ, Docker, MUI, Tailwind, JIRA, and Git.
In C#, exception handling is a mechanism to handle runtime errors, which allows the program to deal with unexpected events (like file not found, database connection failure, etc.) gracefully without crashing. The goal is to catch exceptions, manage them, and provide meaningful feedback to the user or developer.
System Level Exception:
System exceptions are derived from the base class System.SystemException which in itself is a derived class of SystemException.
A System Exception occurs when a fatal or non-recoverable error is encountered, like a database crash, bound errors etc.
Application Level Exception:
Application-level exceptions are derived from the base class System.ApplicationException which is also a derived class of SystemException.
An Application-level exception occurs when a recoverable error is encountered, for example, the wrong type of input data, arithmetic exceptions etc.
Key Concepts of Exception Handling
Exceptions: An exception is an object that represents an error condition in a program. It's derived from the base class
System.Exception.Try-Catch-Finally: This is the main mechanism for handling exceptions in C#.
Try Block: Contains code that may throw exceptions.
Catch Block: Catches and handles the exception.
Finally Block: Optional block that contains code that runs regardless of whether an exception was thrown or not.
Exception Handling Structure: Try-Catch-Finally
The basic syntax for handling exceptions in C# is as follows:
try
{
// Code that may throw an exception
}
catch (ExceptionType ex)
{
// Code that runs if the exception occurs
}
finally
{
// Code that runs regardless of whether an exception occurs or not
}
1. Try Block
The try block is where you place the code that might generate an exception. If an exception occurs within the try block, the runtime jumps to the catch block to handle the exception.
try
{
int result = 10 / 0; // This will throw a DivideByZeroException
}
catch (DivideByZeroException ex)
{
Console.WriteLine("Error: " + ex.Message); // Handle the exception
}
2. Catch Block
The catch block handles the exception thrown by the try block. It can be used to log the exception, show a message, or recover from the exception.
You can catch specific exception types or a generic exception type (base class of all exceptions).
Catching Specific Exceptions
try
{
string[] arr = new string[3];
arr[5] = "Hello"; // This will throw an IndexOutOfRangeException
}
catch (IndexOutOfRangeException ex)
{
Console.WriteLine("Error: " + ex.Message); // Handle specific exception
}
Catching Multiple Exceptions
You can catch multiple types of exceptions in different catch blocks.
try
{
int[] numbers = new int[3];
Console.WriteLine(numbers[5]); // This will throw an IndexOutOfRangeException
}
catch (IndexOutOfRangeException ex)
{
Console.WriteLine("Index Error: " + ex.Message);
}
catch (Exception ex) // Generic exception handler for other types
{
Console.WriteLine("General Error: " + ex.Message);
}
Catching Generic Exception
You can catch all exceptions by using the base Exception class, but it's not recommended because it can hide other errors.
try
{
int result = Convert.ToInt32("abc"); // This will throw a FormatException
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.Message); // Catch all exceptions
}
Using when Clause for Filtering Exceptions
You can filter exceptions by using the when clause to specify a condition under which an exception should be caught.
try
{
int number = int.Parse("abc"); // This will throw FormatException
}
catch (FormatException ex) when (ex.Message.Contains("Input string"))
{
Console.WriteLine("Format error caught: " + ex.Message);
}
3. Finally Block
The finally block contains code that will always run after the try and catch blocks, whether an exception was thrown or not. This is useful for cleanup operations like closing file streams, database connections, etc.
try
{
Console.WriteLine("Attempting to open file...");
// Code that may throw an exception (e.g., file operation)
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.Message); // Handle the exception
}
finally
{
Console.WriteLine("Cleanup or resource release, regardless of success or failure.");
}
Custom Exceptions
You can create your own exceptions by inheriting from the System.Exception class or one of its derived classes.
Defining Custom Exception Class
public class InvalidAgeException : Exception
{
public InvalidAgeException() { }
public InvalidAgeException(string message)
: base(message) { }
public InvalidAgeException(string message, Exception inner)
: base(message, inner) { }
}
Throwing Custom Exceptions
public void CheckAge(int age)
{
if (age < 18)
{
throw new InvalidAgeException("Age cannot be less than 18.");
}
else
{
Console.WriteLine("Valid age.");
}
}
Re-throwing Exceptions
Sometimes you might want to catch an exception, log it or handle it partially, and then re-throw it for further handling higher up the call stack.
try
{
throw new InvalidOperationException("Something went wrong.");
}
catch (InvalidOperationException ex)
{
Console.WriteLine("Caught exception: " + ex.Message);
throw; // Re-throwing the exception
}
Exception Hierarchy
In C#, all exceptions inherit from the base class System.Exception. The most commonly used exceptions are:
SystemException: Base class for predefined exceptions (e.g.,
NullReferenceException,IndexOutOfRangeException,InvalidOperationException).ApplicationException: A base class for custom exceptions, although
SystemExceptionis recommended for most use cases.
Best Practices in Exception Handling
Catch Specific Exceptions: Always try to catch the most specific exception types. Catching
Exceptionis generally discouraged unless you need to handle all exceptions.Don’t Use Exception for Control Flow: Exceptions should be used for exceptional conditions, not for regular control flow in your program.
Avoid Empty Catch Blocks: Don’t catch exceptions without doing anything with them. This can hide bugs and make debugging difficult.
Use Finally for Cleanup: Use the
finallyblock to release resources (e.g., close files, network connections) regardless of whether an exception was thrown.Use
throwto Propagate Exceptions: After handling an exception, you can re-throw it to let higher layers handle it.Log Exceptions: It’s important to log exceptions to a file, event log, or monitoring system, especially for production applications.
Conclusion
Exception handling in C# is a powerful mechanism for managing runtime errors. By using try, catch, and finally blocks, you can ensure that your application responds gracefully to unexpected conditions. Always strive to handle exceptions in a meaningful way, log them, and use custom exceptions when necessary for better error handling in complex applications.