Exception handling is a critical aspect of writing robust and reliable code. In C#, developers traditionally used catch blocks to handle exceptions, but the code often ended up being cluttered with conditional logic. However, with the introduction of C# 7, a new feature called “exception expression” was introduced, which simplifies exception handling and improves code readability. In this article, we will explore the problem code, understand the limitations of the old approach, and see how the exception expression solves those problems effectively.
Problem Code:
Let’s consider a scenario where we have a method that performs some operations and may throw an exception. In the old approach, handling exceptions required multiple catch blocks with conditional statements to identify and handle specific exceptions. Here’s an example:
using System;
public class Program
{
public static void Main()
{
try
{
PerformOperation(0);
}
catch (ArgumentOutOfRangeException ex)
{
Console.WriteLine("Handled ArgumentOutOfRangeException");
}
catch (DivideByZeroException ex)
{
Console.WriteLine("Handled DivideByZeroException");
}
catch (ArgumentException ex)
{
Console.WriteLine("Handled ArgumentException");
}
catch (Exception ex)
{
Console.WriteLine("Handled other exceptions");
}
}
private static void PerformOperation(int value)
{
if (value == 0)
{
throw new ArgumentOutOfRangeException(nameof(value), "Value cannot be zero");
}
else if (value == 1)
{
throw new ArgumentException("Invalid argument");
}
else if (value == 2)
{
throw new DivideByZeroException("Attempted to divide by zero");
}
else
{
Console.WriteLine("Operation performed successfully");
}
}
}
Solution using Exception Expression:
C# 7 introduced the exception expression, which allows us to simplify the code by performing the exception type check and handling in a single line. Let’s refactor the previous code using the exception expression:
using System;
public class Program
{
public static void Main()
{
try
{
PerformOperation(0);
}
catch (Exception ex) when (ex is ArgumentOutOfRangeException || ex is DivideByZeroException)
{
Console.WriteLine("Handled ArgumentOutOfRangeException or DivideByZeroException");
}
catch (ArgumentException ex)
{
Console.WriteLine("Handled ArgumentException");
}
catch
{
Console.WriteLine("Handled other exceptions");
}
}
private static void PerformOperation(int value)
{
if (value == 0)
{
throw new ArgumentOutOfRangeException(nameof(value), "Value cannot be zero");
}
else if (value == 1)
{
throw new ArgumentException("Invalid argument");
}
else if (value == 2)
{
throw new DivideByZeroException("Attempted to divide by zero");
}
else
{
Console.WriteLine("Operation performed successfully");
}
}
}
Let’s break down the code and explain how it works:
-
The
try
block:- It contains the code that may potentially throw an exception.
- In this case, we are calling the
PerformOperation
method with an argument of0
, which will throw anArgumentOutOfRangeException
in the method.
-
The
catch
blocks:- They catch and handle specific types of exceptions.
- The first
catch
block catches anException
(the base class for all exceptions) with awhen
clause. - The
when
clause specifies a condition that must be true for the catch block to execute. - In this case, the condition
(ex is ArgumentOutOfRangeException || ex is DivideByZeroException)
checks if the caught exception is either anArgumentOutOfRangeException
or aDivideByZeroException
. - If the condition is true, the code inside this catch block executes, and it prints “Handled ArgumentOutOfRangeException or DivideByZeroException”.
-
The second
catch
block:- It catches
ArgumentException
specifically. - If the caught exception is an
ArgumentException
, the code inside this catch block executes, and it prints “Handled ArgumentException”.
- It catches
-
The third
catch
block:- It doesn’t specify any exception type, which means it catches any remaining exceptions that are not caught by the previous catch blocks.
- The code inside this catch block executes, and it prints “Handled other exceptions”.
The solution using the exception expression provides a more concise and readable code structure. It allows us to specify the exception type in the catch block itself, eliminating the need for conditional statements. It also enables us to define multiple catch blocks for different exception types, making the code more maintainable and easier to understand.