MSDN Community

Understanding Exceptions in C#

Exceptions are a critical part of robust C# programming. They provide a structured way to handle runtime errors, allowing your application to gracefully recover from unexpected situations.

What are Exceptions?

An exception is an event that occurs during the execution of a program that disrupts the normal flow of the program's instructions. When an exception occurs, an object is created that describes the error that occurred. This object is then "thrown" and the system attempts to "catch" it.

The .NET Framework provides a rich hierarchy of exception classes, all derived from the System.Exception class.

Common Exception Types

  • System.NullReferenceException: Thrown when a null reference is used.
  • System.IndexOutOfRangeException: Thrown when an array index is outside the array's bounds.
  • System.ArgumentException: Thrown when a method receives an argument that is invalid.
  • System.DivideByZeroException: Thrown when an attempt is made to divide an integer by zero.
  • System.IO.FileNotFoundException: Thrown when a file that is expected to be found is not found.

The `try-catch-finally` Block

The primary mechanism for handling exceptions in C# is the try-catch-finally block:


try
{
    // Code that might throw an exception
    int result = Divide(10, 0);
    Console.WriteLine($"Result: {result}");
}
catch (DivideByZeroException ex)
{
    // Handle the DivideByZeroException
    Console.WriteLine($"Error: Cannot divide by zero. {ex.Message}");
}
catch (Exception ex)
{
    // Handle any other type of exception
    Console.WriteLine($"An unexpected error occurred: {ex.Message}");
}
finally
{
    // Code that will always execute, whether an exception occurred or not
    Console.WriteLine("Cleanup operations.");
}

int Divide(int numerator, int denominator)
{
    return numerator / denominator;
}
                
Important: The `finally` block is guaranteed to execute, making it ideal for releasing unmanaged resources or closing connections.

Throwing Exceptions

You can also throw exceptions manually using the throw keyword:


public void ValidateInput(string input)
{
    if (string.IsNullOrEmpty(input))
    {
        throw new ArgumentNullException(nameof(input), "Input cannot be null or empty.");
    }
    // ... further validation
}
                

Custom Exceptions

For domain-specific error conditions, it's good practice to create custom exception classes by deriving from System.Exception:


public class InvalidUserDataException : Exception
{
    public InvalidUserDataException(string message) : base(message) { }
    public InvalidUserDataException(string message, Exception innerException) : base(message, innerException) { }
}
                

Best Practices

  • Catch specific exceptions rather than a general Exception whenever possible.
  • Do not swallow exceptions; log them or re-throw them if you cannot handle them locally.
  • Use try-catch-finally for operations that might fail.
  • Throw exceptions for error conditions that violate a method's contract.
  • Provide meaningful exception messages.
Avoid:

try { /* do something */ } catch { /* do nothing */ }
                    
This hides potential problems.