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.
whenadds an exception filter; if it is false the catch is skipped without unwinding.finallyalways runs, for cleanup. Theusingstatement (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 / checkedRemember
- 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.