Exception Handling

Things go wrong at runtime: a file isn't there, a divisor turns out to be zero, a user types letters into a number field. C# reports these failures as exceptions — objects that carry information about what went wrong — and gives you try/catch to intercept them before they crash your program.

try and catch

Code that might fail goes in a try block. If it throws an exception, execution jumps straight to a matching catch block instead of crashing the program:

</> Program.cs
int[] scores = { 88, 92, 79 };

try
{
    Console.WriteLine(scores[5]);
}
catch (IndexOutOfRangeException e)
{
    Console.WriteLine($"Couldn't read that score: {e.Message}");
}

Console.WriteLine("Program keeps running.");
Output
Couldn't read that score: Index was outside the bounds of the array.
Program keeps running.

scores[5] reaches past the end of a 3-element array, which throws an IndexOutOfRangeException. Without the try/catch, that exception would propagate all the way up and terminate the program. With it, the catch block handles the problem and execution continues normally on the next line.

Catching specific exception types

You can stack multiple catch blocks to handle different failure types differently. C# checks them in order and runs the first one that matches:

</> Program.cs
string input = "abc";

try
{
    int number = int.Parse(input);
    int result = 100 / number;
    Console.WriteLine(result);
}
catch (FormatException)
{
    Console.WriteLine("That wasn't a valid number.");
}
catch (DivideByZeroException)
{
    Console.WriteLine("Can't divide by zero.");
}
Output
That wasn't a valid number.

int.Parse("abc") can't turn that string into a number, so it throws a FormatException and the matching catch runs — the division never even gets attempted. Order matters here too: catch blocks for more specific exception types should come before more general ones, since C# uses the first match it finds top to bottom.

finally: code that always runs

A finally block runs whether or not an exception was thrown — useful for cleanup work like closing a file or a database connection that has to happen either way:

</> Program.cs
try
{
    Console.WriteLine("Opening connection...");
    throw new InvalidOperationException("Connection refused");
}
catch (InvalidOperationException e)
{
    Console.WriteLine($"Failed: {e.Message}");
}
finally
{
    Console.WriteLine("Closing connection.");
}
Output
Opening connection...
Failed: Connection refused
Closing connection.

throw raises an exception manually — here with a message describing what went wrong. finally's "Closing connection." prints regardless of whether the catch block ran at all; if you removed the throw entirely, finally would still execute right after the try block finished normally.

Custom exceptions

For failures specific to your own program's logic, you can define an exception type of your own by inheriting from Exception:

</> Program.cs
class InsufficientFundsException : Exception
{
    public InsufficientFundsException(string message) : base(message)
    {
    }
}

void Withdraw(double balance, double amount)
{
    if (amount > balance)
    {
        throw new InsufficientFundsException($"Can't withdraw {amount}, balance is only {balance}");
    }
    Console.WriteLine("Withdrawal successful.");
}

try
{
    Withdraw(50, 200);
}
catch (InsufficientFundsException e)
{
    Console.WriteLine($"Transaction failed: {e.Message}");
}
Output
Transaction failed: Can't withdraw 200, balance is only 50

A custom exception is a normal class — it just inherits from Exception (or a more specific built-in exception type) so it can be thrown and caught the same way. This lets calling code catch InsufficientFundsException specifically, rather than something generic that could mean anything went wrong.

Note: don't use exceptions for ordinary, expected outcomes — like checking whether a value is present in a list. Exceptions carry real performance overhead and are meant for genuinely exceptional situations. If a "failure" is a normal, expected branch in your logic, an if check or a return value is almost always the better fit.
Course complete: that covers the full C# course — variables, data types, operators, strings, conditionals, loops, arrays and lists, methods, classes and objects, properties and constructors, inheritance and interfaces, and exception handling. Together these are the same fundamentals that carry over into ASP.NET web apps, Unity games, and anything else built on .NET — the syntax around you will change project to project, but the language underneath won't.