C# Exception Basics
Introduction
When you're writing software, things don't always go as planned. Users might enter invalid data, a file you're trying to access might not exist, or a network connection might be lost. In C#, these unexpected situations are represented by exceptions.
An exception is an object that encapsulates information about an unexpected or error condition that occurred while a program was running. Understanding how to work with exceptions is crucial for writing robust and reliable C# applications.
In this tutorial, we'll cover the fundamental concepts of C# exceptions and learn how to handle them effectively.
What is an Exception?
An exception is an object that represents an error condition or unexpected behavior during program execution. When an error occurs, the C# runtime creates an exception object and "throws" it, interrupting the normal flow of program execution.
All exception types in C# derive from the System.Exception
class, which provides properties like:
Message
: A human-readable description of the errorStackTrace
: Information about the call stack when the exception occurredInnerException
: The exception that caused the current exception (if any)Source
: The name of the application or object that caused the error
Common Exception Types
C# provides many built-in exception types for different error scenarios:
NullReferenceException
: Thrown when you try to access a member on a null referenceIndexOutOfRangeException
: Thrown when an array index is outside the bounds of an arrayFileNotFoundException
: Thrown when an attempt to access a file that does not exist failsDivideByZeroException
: Thrown when an integer division by zero occursArgumentException
(and derived types likeArgumentNullException
): Thrown when a method is called with an invalid argumentFormatException
: Thrown when the format of an argument is invalidInvalidOperationException
: Thrown when a method call is invalid for the object's current state
The try-catch Block
The primary mechanism for handling exceptions in C# is the try-catch
block. It allows you to:
- Identify a block of code that might throw an exception (
try
) - Specify how to handle exceptions when they occur (
catch
)
Here's the basic syntax:
try
{
// Code that might throw an exception
}
catch (ExceptionType e)
{
// Code to handle the exception
}
Let's see a simple example:
using System;
class Program
{
static void Main()
{
try
{
Console.Write("Enter a number: ");
string input = Console.ReadLine();
int number = int.Parse(input);
Console.WriteLine($"You entered: {number}");
}
catch (FormatException e)
{
Console.WriteLine($"Error: {e.Message}");
Console.WriteLine("Please enter a valid integer.");
}
}
}
Output (when invalid input like "abc" is entered):
Enter a number: abc
Error: Input string was not in a correct format.
Please enter a valid integer.
Multiple catch Blocks
You can have multiple catch
blocks to handle different types of exceptions:
using System;
using System.IO;
class Program
{
static void Main()
{
try
{
// This code might throw different exceptions
Console.Write("Enter a file path: ");
string filePath = Console.ReadLine();
string content = File.ReadAllText(filePath);
Console.Write("Enter a position to read (as a number): ");
int position = int.Parse(Console.ReadLine());
char character = content[position];
Console.WriteLine($"Character at position {position}: {character}");
}
catch (FileNotFoundException e)
{
Console.WriteLine($"Error: The file was not found.");
Console.WriteLine($"Details: {e.Message}");
}
catch (FormatException e)
{
Console.WriteLine($"Error: The position must be a valid number.");
Console.WriteLine($"Details: {e.Message}");
}
catch (IndexOutOfRangeException e)
{
Console.WriteLine($"Error: The position is outside the content range.");
Console.WriteLine($"Details: {e.Message}");
}
catch (Exception e) // Catch-all for any other exceptions
{
Console.WriteLine($"An unexpected error occurred: {e.Message}");
}
}
}
Important: When using multiple catch
blocks, order them from most specific to most general. A catch block for a base class exception will catch exceptions of any derived types.
The finally Block
The finally
block contains code that is executed whether an exception is thrown or not. This is useful for cleanup operations like closing files or releasing resources:
using System;
using System.IO;
class Program
{
static void Main()
{
StreamReader reader = null;
try
{
reader = new StreamReader("example.txt");
string content = reader.ReadToEnd();
Console.WriteLine(content);
}
catch (FileNotFoundException)
{
Console.WriteLine("The file 'example.txt' was not found.");
}
catch (Exception e)
{
Console.WriteLine($"An error occurred: {e.Message}");
}
finally
{
// This code runs whether an exception occurred or not
if (reader != null)
{
reader.Close();
Console.WriteLine("StreamReader closed.");
}
}
}
}
Throwing Exceptions
Sometimes you might need to throw exceptions yourself, especially when validating parameters or when an operation can't proceed normally:
using System;
class Calculator
{
public static double Divide(double dividend, double divisor)
{
if (divisor == 0)
{
throw new ArgumentException("Cannot divide by zero", nameof(divisor));
}
return dividend / divisor;
}
}
class Program
{
static void Main()
{
try
{
double result = Calculator.Divide(10, 0);
Console.WriteLine($"Result: {result}");
}
catch (ArgumentException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
Output:
Error: Cannot divide by zero
Re-throwing Exceptions
Sometimes, after catching an exception, you might want to perform some action and then re-throw the exception to let it propagate up the call stack:
using System;
class Program
{
static void Main()
{
try
{
ProcessFile("nonexistent.txt");
}
catch (Exception e)
{
Console.WriteLine($"Main caught: {e.Message}");
}
}
static void ProcessFile(string filePath)
{
try
{
string content = System.IO.File.ReadAllText(filePath);
Console.WriteLine(content);
}
catch (System.IO.FileNotFoundException e)
{
Console.WriteLine($"ProcessFile logged: File not found.");
// Re-throw the same exception
throw;
// Alternative: throw a new exception with the original as the inner exception
// throw new ApplicationException("Could not process file", e);
}
}
}
Output:
ProcessFile logged: File not found.
Main caught: Could not find file 'C:\path\to\nonexistent.txt'.
Real-World Example: User Input Validation
Let's see a more practical example of exception handling for validating user input in a console application:
using System;
class Program
{
static void Main()
{
bool isValid = false;
int age = 0;
while (!isValid)
{
try
{
Console.Write("Please enter your age: ");
string input = Console.ReadLine();
age = int.Parse(input);
if (age < 0 || age > 120)
{
throw new ArgumentOutOfRangeException("Age must be between 0 and 120");
}
isValid = true;
}
catch (FormatException)
{
Console.WriteLine("Error: Please enter a valid number.");
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine($"Error: {e.Message}");
}
catch (Exception e)
{
Console.WriteLine($"Unexpected error: {e.Message}");
}
}
Console.WriteLine($"Thank you! You are {age} years old.");
// Continue with the rest of the program...
if (age < 18)
{
Console.WriteLine("You are a minor.");
}
else
{
Console.WriteLine("You are an adult.");
}
}
}
This example shows a common pattern: using a loop to repeatedly ask for input until valid data is provided, using exceptions to handle validation failures.
Best Practices for Exception Handling
- Don't overuse exceptions for flow control — they're for exceptional circumstances.
- Always catch specific exceptions rather than catching
Exception
when possible. - Order catch blocks from most specific to most general.
- Use finally blocks to ensure cleanup code always runs.
- Include meaningful information when throwing custom exceptions.
- Log exceptions for debugging purposes.
- Only catch exceptions you can handle meaningfully.
- Consider using try-catch only at appropriate levels of your application (not necessarily everywhere).
Summary
In this tutorial, you've learned the basics of exception handling in C#:
- What exceptions are and how they work
- Common exception types provided by the .NET framework
- How to use try-catch blocks to handle exceptions
- Using multiple catch blocks for different exception types
- The purpose of the finally block for cleanup operations
- How to throw and re-throw exceptions
- A real-world example of input validation using exceptions
- Best practices for effective exception handling
Exception handling is a critical skill for C# developers. Proper exception handling makes your applications more robust, user-friendly, and easier to debug.
Exercises
- Write a program that asks the user for two numbers and divides the first by the second. Handle all potential exceptions.
- Create a method that reads a text file and counts the number of words. Use proper exception handling.
- Write a simple calculator program that handles various operations (+, -, *, /) and uses exceptions for validation.
- Create a custom exception class called
AgeException
that is thrown when an age value is outside a valid range. - Modify the input validation example to handle a list of ages for multiple people, catching and reporting all errors.
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)