DEV Community

mohamed Tayel
mohamed Tayel

Posted on

Mastering C# Fundamentals: Exception Handling

Meta Description: Learn how to effectively handle exceptions in C# using try/catch blocks. Understand the importance of ordering catch blocks, why Exception should be placed last, and practice with assignments from easy to difficult levels to master error handling.

In software development, errors are inevitable. No matter how well we plan, test, or try to prevent them, our applications will encounter unexpected situations that can cause failures. These errors, known as exceptions, can occur during runtime and disrupt the normal flow of a program, potentially leading to crashes. Imagine a scenario: a user provides an unexpected input, such as dividing a number by zero. Such actions lead to a runtime exception, as division by zero is undefined.

Exceptions can also be caused by external factors, such as a file permission issue, database connectivity problems, or operating system restrictions. In this article, we'll discuss how C# provides a structured way to handle exceptions using the try/catch block to ensure errors are handled gracefully without crashing the application.

The Try/Catch Block in C#

C# provides a try/catch mechanism to handle exceptions. By placing potentially problematic code inside a try block, we can catch any exceptions in the catch block and deal with them appropriately. Here’s the syntax:

try
{
    // Code that might throw an exception
}
catch (Exception ex)
{
    // Code to handle the exception
}
Enter fullscreen mode Exit fullscreen mode

The try block contains the code that you think might throw an exception, while the catch block handles it if an exception is thrown. This approach ensures the application doesn't crash, and we can gracefully inform users or take corrective actions.

Ordering Catch Blocks and Using Exception

One key point to remember when using multiple catch blocks is the order in which they are written. In C#, exceptions form a hierarchy, with Exception being the base class for all exceptions. If you use multiple catch blocks, you need to sort them from the most specific exception type to the most general exception type. This is because once a catch block matches an exception, the subsequent catch blocks will not be executed.

Why Place Exception Last?

The Exception class is the base class for all exceptions, meaning it can catch any type of exception. If you put it first, the subsequent, more specific catch blocks would never be reached, because Exception would handle all errors before they had the chance to be caught by a more specific handler. Placing Exception last ensures that the most appropriate handler is used first.

Example: Ordering Catch Blocks

try
{
    Console.Write("Enter a number: ");
    int number = int.Parse(Console.ReadLine());

    Console.Write("Enter divisor: ");
    int divisor = int.Parse(Console.ReadLine());

    int result = number / divisor;
    Console.WriteLine($"Result: {result}");
}
catch (DivideByZeroException)
{
    Console.WriteLine("Error: Division by zero is not allowed.");
}
catch (FormatException)
{
    Console.WriteLine("Error: Please enter a valid number.");
}
catch (Exception ex)
{
    Console.WriteLine("An unexpected error occurred: " + ex.Message);
}
Enter fullscreen mode Exit fullscreen mode

In this example:

  1. Specific exceptions (DivideByZeroException and FormatException) are caught first.
  2. The general Exception is caught last, which acts as a "catch-all" to handle any unforeseen exceptions.

This ordering ensures that:

  • If a specific type of exception is thrown (e.g., DivideByZeroException), the corresponding catch block handles it directly.
  • The general Exception block will only be used if the thrown exception does not match any specific type above it.

Example: Exception Handling with File Operations

Let’s consider a file processing application where we need to read a configuration file. The application should handle scenarios where the file is missing, inaccessible, or empty:

try
{
    Console.Write("Enter the path of the configuration file: ");
    string filePath = Console.ReadLine();

    if (string.IsNullOrWhiteSpace(filePath))
    {
        throw new ArgumentNullException("File path cannot be empty.");
    }

    string content = System.IO.File.ReadAllText(filePath);

    if (string.IsNullOrWhiteSpace(content))
    {
        throw new InvalidOperationException("Configuration file is empty.");
    }

    Console.WriteLine("File content:");
    Console.WriteLine(content);
}
catch (System.IO.FileNotFoundException)
{
    Console.WriteLine("Error: The file was not found. Please check the file path.");
}
catch (UnauthorizedAccessException)
{
    Console.WriteLine("Error: You do not have permission to access this file.");
}
catch (ArgumentNullException ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}
catch (InvalidOperationException ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}
catch (Exception ex)
{
    Console.WriteLine("An unexpected error occurred: " + ex.Message);
}
Enter fullscreen mode Exit fullscreen mode

In this example, we order the catch blocks:

  • First, we catch file-specific exceptions (FileNotFoundException, UnauthorizedAccessException).
  • Then, we handle more general exceptions (ArgumentNullException, InvalidOperationException).
  • Lastly, we use the Exception block to catch any other unexpected errors.

Assignments to Practice Exception Handling

Here are assignments at different difficulty levels to help you understand and apply these concepts.


Assignment Levels

1. Easy Level: Temperature Converter

Create an application that:

  1. Asks the user to enter a temperature in Celsius.
  2. Converts it to Fahrenheit.
  3. If the input is not a valid number, catch the appropriate exception and prompt the user to enter a valid number.

Example:

try
{
    Console.Write("Enter temperature in Celsius: ");
    double celsius = double.Parse(Console.ReadLine());
    double fahrenheit = (celsius * 9 / 5) + 32;
    Console.WriteLine($"Temperature in Fahrenheit: {fahrenheit}");
}
catch (FormatException)
{
    Console.WriteLine("Error: Please enter a valid number.");
}
catch (Exception ex)
{
    Console.WriteLine("An unexpected error occurred: " + ex.Message);
}
Enter fullscreen mode Exit fullscreen mode

2. Medium Level: Employee Data Management

Write a program that:

  1. Asks the user to enter employee details (Name, Age, Salary).
  2. Validates the data: age must be between 18 and 65, and salary must be positive.
  3. Throws an exception if the input is invalid and catches it to display an appropriate message.

Example:

try
{
    Console.Write("Enter employee name: ");
    string name = Console.ReadLine();

    Console.Write("Enter employee age: ");
    int age = int.Parse(Console.ReadLine());

    if (age < 18 || age > 65)
    {
        throw new ArgumentOutOfRangeException("Age must be between 18 and 65.");
    }

    Console.Write("Enter employee salary: ");
    decimal salary = decimal.Parse(Console.ReadLine());

    if (salary <= 0)
    {
        throw new ArgumentOutOfRangeException("Salary must be positive.");
    }

    Console.WriteLine($"Employee {name}, Age: {age}, Salary: {salary:C}");
}
catch (FormatException)
{
    Console.WriteLine("Error: Please enter a valid number for age or salary.");
}
catch (ArgumentOutOfRangeException ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}
catch (Exception ex)
{
    Console.WriteLine("An unexpected error occurred: " + ex.Message);
}
Enter fullscreen mode Exit fullscreen mode

3. Difficult Level: Product Inventory Management

Create an inventory management application:

  1. Allows adding, removing, and viewing products.
  2. Each product has a name, price, and quantity.
  3. Throws a custom ProductNotFoundException if the user attempts to remove a product that doesn’t exist.
  4. Handles ArgumentOutOfRangeException if an invalid quantity or price is entered.

Example:

public class ProductNotFoundException : Exception
{
    public ProductNotFoundException(string productName) 
        : base($"Product '{productName}' was not found in inventory.") { }
}

try
{
    var inventory = new Dictionary<string, (decimal Price, int Quantity)>();

    Console.WriteLine("Add a product:");
    Console.Write("Product name: ");
    string productName = Console.ReadLine();

    Console.Write("Product price: ");
    decimal price = decimal.Parse(Console.ReadLine());

    if (price <= 0)
    {
        throw new ArgumentOutOfRangeException("Price must be greater than zero.");
    }

    Console.Write("Product quantity: ");
    int quantity = int.Parse(Console.ReadLine());

    if (quantity < 0)
    {
        throw new ArgumentOutOfRangeException("Quantity cannot be negative.");
    }

    inventory[productName] = (price, quantity);
    Console.WriteLine($"Product '{productName}' added with price {price:C} and quantity {quantity}.");

    Console.WriteLine("Remove a product:");
    Console.Write("Enter product name to remove: ");
    string productToRemove = Console.ReadLine();

    if (!inventory.ContainsKey(productToRemove))
    {
        throw new ProductNotFoundException(productToRemove);
    }

    inventory.Remove(productToRemove);
    Console.WriteLine($"Product '{productToRemove}' removed from inventory.");
}
catch (FormatException)
{
    Console.WriteLine("Error: Please enter a valid number for price or quantity.");
}
catch (ArgumentOutOfRangeException ex)
{
    Console.WriteLine($"Error: {ex.Message}");


}
catch (ProductNotFoundException ex)
{
    Console.WriteLine(ex.Message);
}
catch (Exception ex)
{
    Console.WriteLine("An unexpected error occurred: " + ex.Message);
}
Enter fullscreen mode Exit fullscreen mode

Summary

Exception handling is crucial for developing robust and user-friendly applications. With the use of try/catch blocks, we can prevent our application from crashing and provide meaningful feedback to users when something goes wrong. By ordering catch blocks properly—placing the most specific exceptions first and the general Exception last—we ensure that our code handles errors appropriately and predictably. Practice these examples to gain a better understanding of handling errors gracefully in C#.

Top comments (0)