C# Exceptions

Handling errors gracefully in your applications

โš ๏ธ What are Exceptions?

Exceptions are errors that occur during program execution. C# provides a structured way to handle these errors using try-catch blocks, preventing your application from crashing unexpectedly.


// Simple exception handling example
try
{
    int result = 10 / 0; // This will cause an error
}
catch (Exception ex)
{
    Console.WriteLine("Error: " + ex.Message);
}
                                    

Output:

Error: Attempted to divide by zero.

Key Exception Concepts

๐Ÿ›ก๏ธ

Try-Catch

Protect code from errors

try { /* code */ }
catch { /* handle */ }
๐ŸŽฏ

Specific Exceptions

Catch different error types

catch (DivideByZeroException ex)
{ /* handle division */ }
๐Ÿงน

Finally Block

Always execute cleanup code

finally
{ /* cleanup code */ }
๐Ÿš€

Throw

Create custom exceptions

throw new Exception
("Custom error");

๐Ÿ”น Basic Try-Catch Structure

The try-catch block is the fundamental construct for structured exception handling in C#. Code that might generate a runtime error is placed within the try block. If an exception occurs, execution immediately jumps to the corresponding catch block, where you can log the error, notify the user, or attempt recovery. This prevents the application from crashing and allows for graceful error management. For example, wrapping user input parsing in a try-catch prevents crashes when non-numeric values are entered.

using System;

class Program
{
    static void Main()
    {
        try
        {
            Console.Write("Enter a number: ");
            int number = int.Parse(Console.ReadLine());
            Console.WriteLine("You entered: " + number);
        }
        catch (FormatException)
        {
            Console.WriteLine("That's not a valid number!");
        }
    }
}

Output (if user enters "abc"):

Enter a number: abc

That's not a valid number!

๐Ÿ”น Multiple Catch Blocks

Using multiple catch blocks allows you to handle different exception types with tailored responses. You can catch a specific exception like FormatException for parsing errors and a more general Exception as a fallback. The runtime evaluates catch blocks from most specific to least specific, so order matters. This precision enables you to provide user-friendly messages for anticipated errors (e.g., "Invalid number format") while still logging unexpected system-level exceptions for debugging, greatly improving the user experience and maintainability.

using System;

class Program
{
    static void Main()
    {
        try
        {
            int[] numbers = { 1, 2, 3 };
            Console.WriteLine(numbers[10]); // Index out of range
        }
        catch (IndexOutOfRangeException)
        {
            Console.WriteLine("Index is out of range!");
        }
        catch (DivideByZeroException)
        {
            Console.WriteLine("Cannot divide by zero!");
        }
        catch (Exception ex)
        {
            Console.WriteLine("General error: " + ex.Message);
        }
    }
}

Output:

Index is out of range!

๐Ÿ”น Finally Block

The finally block guarantees execution of cleanup code, regardless of whether an exception was thrown or caught. This makes it indispensable for resource management, such as closing file handles, database connections, or network streams. Whether the try block succeeds, throws a handled exception, or even an unhandled one, the finally block runs. This ensures your application doesn't leak resources, which is critical for stability and performance in long-running services and applications.

using System;

class Program
{
    static void Main()
    {
        try
        {
            Console.WriteLine("Opening file...");
            int result = 10 / 0; // Error occurs
        }
        catch (Exception ex)
        {
            Console.WriteLine("Error: " + ex.Message);
        }
        finally
        {
            Console.WriteLine("Closing file... (always runs)");
        }
    }
}

Output:

Opening file...

Error: Attempted to divide by zero.

Closing file... (always runs)

๐Ÿ”น Throwing Exceptions

You can proactively throw exceptions to enforce application rules and validate state using the throw keyword. This is essential for input validation, business logic enforcement, and signaling unrecoverable errors. Always throw the most appropriate exception type (e.g., ArgumentException for bad input) and provide a clear, descriptive message. Custom exception classes can be created for domain-specific errors. Remember that throwing an exception should indicate a truly exceptional condition that the immediate caller cannot resolve.

using System;

class Program
{
    static void CheckAge(int age)
    {
        if (age < 18)
        {
            throw new Exception("You must be 18 or older!");
        }
        Console.WriteLine("Access granted!");
    }

    static void Main()
    {
        try
        {
            CheckAge(15);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Error: " + ex.Message);
        }
    }
}

Output:

Error: You must be 18 or older!

๐Ÿ”น Common Exception Types

C# provides a rich hierarchy of built-in exception types to accurately represent different failure modes. Key types include NullReferenceException (accessing null object members), ArgumentException (invalid method arguments), FormatException (invalid string format), IndexOutOfRangeException (invalid array index), and IOException (file I/O errors). Using the correct type, rather than a generic Exception, makes error handling more precise, debug logs more informative, and your API's contract clearer to other developers.

using System;

class Program
{
    static void Main()
    {
        // NullReferenceException
        string text = null;
        // Console.WriteLine(text.Length); // Error!

        // ArgumentNullException
        // ProcessData(null); // Error!

        // InvalidOperationException
        // Used when operation is invalid for current state

        // FileNotFoundException
        // When trying to open a file that doesn't exist

        Console.WriteLine("Exception types demonstrated!");
    }
}

Most Common Exceptions:

  • NullReferenceException: Accessing null object
  • IndexOutOfRangeException: Array index invalid
  • DivideByZeroException: Division by zero
  • FormatException: Invalid format conversion
  • ArgumentException: Invalid argument passed

๐Ÿง  Test Your Knowledge

Which block always executes, regardless of whether an exception occurs?