C# -

Exception Handling

Exception handling is a fundamental part of writing robust and reliable C# applications. This tutorial will cover the basics of exception handling, various techniques, best practices, and advanced concepts.


1. Introduction to Exception Handling

Exception handling in C# is used to manage errors and unexpected events in a controlled manner. This ensures that your application can handle runtime errors gracefully and continue to operate or fail gracefully.


2. Basic Try-Catch Block

The basic try-catch block is used to catch and handle exceptions. Here's an example:

        
            using System;

namespace ExceptionHandlingExamples;

public class Program
{
    public static void Main(string[] args)
    {
        try
        {
            int result = 10 / 0; // This will cause a DivideByZeroException
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine($"An error occurred: {ex.Message}");
        }
    }
}
        
    

3. Catching Specific Exceptions

You can catch specific exceptions to handle different types of errors appropriately. Here's how:

        
            using System;

namespace ExceptionHandlingExamples;

public class Program
{
    public static void Main(string[] args)
    {
        try
        {
            int[] numbers = { 1, 2, 3 };
            Console.WriteLine(numbers[5]); // This will cause an IndexOutOfRangeException
        }
        catch (IndexOutOfRangeException ex)
        {
            Console.WriteLine($"Index out of range: {ex.Message}");
        }
    }
}
        
    

4. Using Finally Block

The finally block is used to execute code regardless of whether an exception was thrown or not. Here's an example:

        
            using System;

namespace ExceptionHandlingExamples;

public class Program
{
    public static void Main(string[] args)
    {
        try
        {
            int result = 10 / 0; // This will cause a DivideByZeroException
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine($"An error occurred: {ex.Message}");
        }
        finally
        {
            Console.WriteLine("This will always be executed.");
        }
    }
}
        
    

5. Throwing Exceptions

You can throw exceptions using the throw keyword. This is useful for indicating errors in your methods. Here's how:

        
            using System;

namespace ExceptionHandlingExamples;

public class Program
{
    public static void Main(string[] args)
    {
        try
        {
            ValidateAge(-1);
        }
        catch (ArgumentException ex)
        {
            Console.WriteLine($"Validation error: {ex.Message}");
        }
    }

    public static void ValidateAge(int age)
    {
        if (age < 0)
        {
            throw new ArgumentException("Age cannot be negative");
        }
    }
}
        
    

6. Custom Exceptions

Custom exceptions allow you to define your own exception types. This is useful for domain-specific error handling. Here's an example:

        
            using System;

namespace ExceptionHandlingExamples;

public class InvalidOrderException : Exception
{
    public InvalidOrderException(string message) : base(message) { }
}

public class Program
{
    public static void Main(string[] args)
    {
        try
        {
            ProcessOrder(-1);
        }
        catch (InvalidOrderException ex)
        {
            Console.WriteLine($"Order processing error: {ex.Message}");
        }
    }

    public static void ProcessOrder(int orderId)
    {
        if (orderId <= 0)
        {
            throw new InvalidOrderException("Order ID must be greater than zero");
        }
    }
}
        
    

7. Exception Properties

Exceptions in C# have several useful properties, such as Message, StackTrace, and InnerException. Here's how to use them:

        
            using System;

namespace ExceptionHandlingExamples;

public class Program
{
    public static void Main(string[] args)
    {
        try
        {
            int result = 10 / 0; // This will cause a DivideByZeroException
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine($"Exception Message: {ex.Message}");
            Console.WriteLine($"Stack Trace: {ex.StackTrace}");
        }
    }
}
        
    

8. Inner Exceptions

Inner exceptions are used to capture the original exception in a chain of exceptions. Here's how to handle them:

        
            using System;

namespace ExceptionHandlingExamples;

public class Program
{
    public static void Main(string[] args)
    {
        try
        {
            CauseInnerException();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Outer Exception: {ex.Message}");
            if (ex.InnerException != null)
            {
                Console.WriteLine($"Inner Exception: {ex.InnerException.Message}");
            }
        }
    }

    public static void CauseInnerException()
    {
        try
        {
            int result = 10 / 0; // This will cause a DivideByZeroException
        }
        catch (DivideByZeroException ex)
        {
            throw new InvalidOperationException("An error occurred in the inner method", ex);
        }
    }
}
        
    

9. Global Exception Handling

Global exception handling is used to catch and handle exceptions at the application level. Here's an example:

        
            using System;
using System.Threading.Tasks;

namespace ExceptionHandlingExamples;

public class Program
{
    public static void Main(string[] args)
    {
        AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionTrapper;
        
        TaskScheduler.UnobservedTaskException += (sender, eventArgs) =>
        {
            Console.WriteLine($"Unobserved Task Exception: {eventArgs.Exception.Message}");
            eventArgs.SetObserved();
        };

        throw new Exception("Global exception test");
    }

    static void UnhandledExceptionTrapper(object sender, UnhandledExceptionEventArgs e)
    {
        Console.WriteLine($"Unhandled exception: {((Exception)e.ExceptionObject).Message}");
    }
}
        
    

10. Exception Filters

Exception filters provide a way to execute code conditionally based on the type or properties of an exception. Here's how to use them:

        
            using System;

namespace ExceptionHandlingExamples;

public class Program
{
    public static void Main(string[] args)
    {
        try
        {
            int result = 10 / 0; // This will cause a DivideByZeroException
        }
        catch (DivideByZeroException ex) when (ex.Message.Contains("zero"))
        {
            Console.WriteLine("Caught a divide by zero exception.");
        }
    }
}
        
    

11. Best Practices for Exception Handling

Here are some best practices for handling exceptions in C#:


12. Advanced Exception Handling

Advanced exception handling techniques include custom logging, wrapping exceptions, and using patterns like retry logic. Here's an example:

        
            using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace ExceptionHandlingExamples;

public class Program
{
    public static async Task Main(string[] args)
    {
        try
        {
            await RetryPolicy.ExecuteAsync(async () =>
            {
                var client = new HttpClient();
                var response = await client.GetAsync("https://nonexistent.url");
                response.EnsureSuccessStatusCode();
            });
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Final failure: {ex.Message}");
        }
    }
}

public static class RetryPolicy
{
    public static async Task ExecuteAsync(Func<Task> action, int retryCount = 3)
    {
        for (int i = 0; i < retryCount; i++)
        {
            try
            {
                await action();
                return;
            }
            catch
            {
                if (i == retryCount - 1)
                {
                    throw;
                }
            }
        }
    }
}
        
    


13. Conclusion

Exception handling is a critical aspect of writing robust C# applications. By understanding and applying best practices, you can handle errors gracefully and maintain the stability of your application.