The call
An exception is an object that reports an error a method cannot fix where it happens. Throwing one stops the normal flow, and .NET looks for code that can handle it. Here, CheckAge refuses a negative age.
Line 1 opens a try block: code that might fail. Line 2 calls CheckAge(-5). Each call gets a frame on the call stack, holding its parameters and where to return. CheckAge's frame sits on top of Main's.
Inside CheckAge, line 2 tests age < 0. The frame says age is -5, so the test is true and line 3 runs. CheckAge cannot return a sensible age, so it will report the problem instead of guessing.
Throw and unwind
new ArgumentException(...) builds an exception object on the heap; its Message is "Age cannot be negative". throw hands it to the runtime, which fills in the stack trace. CheckAge stops right there.
Line 5, return age, never runs. CheckAge has no catch, so the runtime leaves it: its frame is popped off the stack. This is called unwinding. The exception moves down to the caller, Main.
Catch and finally
The call on line 2 never finished, so a is never set and line 3 is skipped. Main is inside a try, so the runtime checks its catch: the exception is an ArgumentException, so it matches, and e refers to the object.
Line 5 runs: Console.WriteLine(e.Message) prints "Age cannot be negative". The exception is now handled. The program does not crash; once the finally block has run, it carries on after the try statement.
The finally block runs last and prints done. It runs when the try block finishes normally and also after a catch has handled an exception. Use it for clean-up that must always happen, such as closing a file.
Exceptions are classes. ArgumentException derives from SystemException, which derives from Exception. A catch takes its own type or any derived type, so catch the specific types first; catch (Exception) catches everything.
What happens
// step 1/9 · idea
exception an object of a class derived from System.Exception
Message what went wrong (text)
StackTrace where it happened (the chain of calls)
throw stops the method at once
try/catch marks the code that can handle it
The call stack is drawn simply: one block per call holding its parameters and locals. Real frames also hold the return address and saved registers, and the JIT may keep small values in CPU registers instead. If no catch anywhere on the stack matches, the exception is unhandled: .NET ends the process and prints "Unhandled exception." with the type, message and stack trace to standard error. For an unhandled exception, Microsoft does not guarantee that finally blocks run.