Exceptions

C# · Essentials · 10 min

Errors in .NET are reported as exceptions; handling them in the right place keeps programs robust.

When something goes wrong, .NET code throws an exception object derived from System.Exception. It unwinds the call stack until a matching catch handles it; if none does, the program crashes with the stack trace.

try { ... }
catch (FormatException ex) { ... }
catch (Exception ex) when (ex.Message.Contains("x")) { ... }
finally { ... }
  • Catch blocks are tried in order, so put specific types before general ones.
  • when adds an exception filter; if it is false the catch is skipped without unwinding.
  • finally always runs, for cleanup. The using statement (covered later) is the usual way to dispose resources.
  • Inside a catch, throw; rethrows preserving the original stack trace; throw ex; resets it.

Throw built-in types where they fit: ArgumentException, ArgumentNullException, ArgumentOutOfRangeException for bad arguments, InvalidOperationException when the object's state doesn't allow the call, NotSupportedException, KeyNotFoundException. Create your own by deriving from Exception when callers need to catch it specifically.

Guidelines: only catch exceptions you can handle; let others propagate to a top-level handler that logs them. Don't use exceptions for normal control flow: prefer TryParse-style methods when failure is expected. Never swallow exceptions with an empty catch.

Example

static int ParseAge(string s)
{
    int age = int.Parse(s);                 // may throw FormatException
    if (age < 0) throw new ArgumentOutOfRangeException(nameof(s), "negative age");
    return age;
}

foreach (var input in new[] { "42", "abc", "-3" })
{
    try
    {
        Console.WriteLine($"ok {ParseAge(input)}");
    }
    catch (FormatException)
    {
        Console.WriteLine($"not a number: {input}");
    }
    catch (ArgumentException ex) when (ex.ParamName == "s")
    {
        Console.WriteLine($"invalid: {ex.Message.Split(" (")[0]}");
    }
    finally { Console.WriteLine("checked"); }
}
// ok 42 / checked / not a number: abc / checked / invalid: negative age / checked

Remember

  • Order catch blocks from specific to general
  • Use throw; to rethrow without losing the stack trace
  • finally always runs; when filters a catch
  • Catch only what you can handle

Watch out for

  • Wrapping everything in catch (Exception) { } and hiding real bugs.

Check yourself

  1. Why prefer throw; over throw ex; inside a catch block?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: learn.microsoft.com/en-us/dotnet/csharp/fundamentals/exceptions/