Menu

C# try catch: Handling Exceptions with catch, when and finally

try, catch and finally are how C# handles exceptions. Learn how an exception travels up the call stack, how to catch specific exception types in the right order, filter with when, clean up in finally, rethrow with throw; without losing the stack trace, and which exceptions not to catch.

This page includes runnable editors - edit, run, and see output instantly.

When something goes wrong at run time (a file is missing, text is not a number, a key is not in a dictionary), .NET throws an exception: an object describing the error. The exception travels up the call stack until a catch block handles it. If nothing does, the program stops and prints the error.

A basic try catch

Wrap the code that can fail in try, and handle the failure in catch:

Output:

42 doubled is 84
'forty-two' is not a number
7 doubled is 14
Still running

When int.Parse("forty-two") throws, the Console.WriteLine after it in the try block is skipped, the catch (FormatException) block runs, and the loop continues. You can leave out the variable (catch (FormatException)) when you do not need the exception object.

This particular case has a better tool: int.TryParse(input, out int n) returns false instead of throwing. Exceptions are for situations the code does not expect; input that is often invalid is expected, so check it instead of catching.

How an exception travels

An exception thrown deep inside a call chain unwinds every method until it finds a matching handler. Code after the throw in each of those methods does not run.

Output:

OrderTotal finished
5.00
Caught KeyNotFoundException in Main

PriceOf and OrderTotal have no catch, so the exception passes straight through them to Main. Put a handler at the level that knows what to do about the failure, which is often not where it happened.

Catching specific exceptions, in the right order

A catch clause handles its type and every type derived from it. When there are several clauses, the first one that matches wins, so order them from most specific to most general:

Output:

10 / 2 = 5
Cannot divide by zero
Both values must be whole numbers
Unexpected: OverflowException

The last call throws OverflowException, which neither specific clause handles, so the general catch (Exception e) does. Putting catch (Exception) first would make the clauses after it unreachable, and the compiler rejects that with error CS0160.

Exception filters with when

C# 6 added when: a condition that decides whether a catch clause applies. If it is false, the runtime carries on looking for another handler as if the clause did not exist.

Output:

Not found: show an empty page
Server error 503: retry later
Unhandled by Call: HTTP 401

Filters let you branch on data inside the exception without catching and rethrowing. They are also the clean way to handle two unrelated types identically, as the third clause does. A filter runs before the stack is unwound, so a debugger or crash dump still shows the original state when no filter matches.

finally: code that always runs

A finally block runs when control leaves the try, whether it finished, returned early, or threw. It is where cleanup goes. (One caveat: if nothing anywhere catches the exception, the process can end without running it.)

Output:

Open connection
Close connection
finished
Open connection
Close connection
returned early
Open connection
Close connection
handled error

In every case "Close connection" prints before the method's result reaches Main: the finally runs after the return value is computed but before the method actually returns. A try can have a finally with no catch at all, which cleans up while letting the exception continue to the caller.

For objects that implement IDisposable (files, streams, connections), the using statement writes this try/finally for you.

Rethrowing: throw; vs throw e;

Sometimes a catch block logs or records something and then lets the exception continue. How you rethrow decides whether the stack trace survives:

Output:

throw;   trace mentions LoadConfig: True
throw e; trace mentions LoadConfig: False

throw e; treats the exception as newly thrown from the catch block, so the frames below it, including the method where the error happened, are gone from the trace. Always rethrow with a bare throw;. (The NoInlining attribute is only there because the JIT may merge a method this small into its caller, which would hide it from both traces.)

To add context instead, wrap the exception in a new one and pass the original as the inner exception: throw new ConfigException("Could not start the app", e);. The inner exception keeps its own stack trace, and loggers print the chain. Writing your own exception types is covered in throwing exceptions.

The Exception object

Every exception derives from System.Exception. The members you use most:

MemberWhat it holds
MessageA human readable description
GetType().NameThe exception type, such as FormatException
StackTraceThe chain of method calls at the throw point
InnerExceptionThe exception that caused this one, or null
ToString()Type, message, inner exceptions and stack trace together

Log e.ToString() rather than e.Message when you want to diagnose a failure later: the message alone rarely says where the problem was. Do not show e.ToString() to end users.

Common exception types

ExceptionTypical cause
NullReferenceExceptionCalling a member on a null reference
ArgumentNullExceptionA method was passed null where it needs a value
ArgumentOutOfRangeExceptionAn argument or list index is outside the allowed range
IndexOutOfRangeExceptionAn array index is outside its bounds
FormatExceptionint.Parse, DateTime.Parse and friends on text in the wrong format
InvalidCastExceptionAn explicit cast to a type the object is not
InvalidOperationExceptionThe object is in the wrong state for the call (empty sequence, modified collection)
KeyNotFoundExceptionReading a missing dictionary key with the indexer
DivideByZeroExceptionInteger or decimal division by zero
OverflowExceptionA parsed number, checked conversion or checked arithmetic result does not fit the type
FileNotFoundException, IOExceptionFile system problems

NullReferenceException, IndexOutOfRangeException and InvalidCastException almost always mean a bug. Fix the code rather than catching them.

Not swallowing exceptions

An empty catch hides every error, including the ones you did not anticipate:

try
{
    SaveOrder(order);
}
catch (Exception)
{
    // nothing: the order silently was not saved
}

The program keeps running as if the save worked, and the real cause is lost. Guidelines that keep exception handling honest:

  • Catch only what you can handle, at the level that can handle it.
  • If you catch to log, rethrow with throw; unless the program can really continue.
  • Catch Exception only at the outer edge: Main, a request handler, a worker loop.
  • Prefer TryParse, TryGetValue and null checks over exceptions for expected cases. Throwing is slow compared to a check, while a try block that throws nothing costs almost nothing.

Common mistakes

  • catch (Exception) first. Later clauses become unreachable (CS0160).
  • throw e; to rethrow. It erases the original stack trace; use throw;.
  • Empty catch blocks. Errors vanish; at least log and rethrow.
  • Using exceptions for control flow. Validate input with TryParse instead of catching FormatException.
  • Showing e.Message of a framework exception to users. The wording differs between .NET versions and is written for developers.

Frequently Asked Questions

How does try catch work in C#?

Code that might fail goes in the try block. If a statement there throws an exception, the rest of the block is skipped and the runtime looks for a catch clause whose type matches the exception, first in the current method and then in each caller. The first matching catch runs, and execution continues after the whole try statement.

Does finally always run in C#?

Almost always: after the try block completes normally, after a catch handles an exception, after a return or break inside the block, and when an exception passes through on its way to a catch further up the call stack. It does not run when the process ends first: a killed process, Environment.FailFast, a StackOverflowException, and on .NET Core and later an exception that nothing catches, which terminates the process before finally blocks run.

How do I catch multiple exceptions in C#?

Write several catch clauses, most specific type first: catch (FileNotFoundException) before catch (IOException) before catch (Exception). The compiler rejects a clause that can never be reached because an earlier one already catches its type. To handle two unrelated types the same way, use a filter: catch (Exception e) when (e is FormatException || e is OverflowException).

What is the difference between throw and throw ex in C#?

Inside a catch, throw; rethrows the current exception with its original stack trace. throw ex; throws the same object but resets the stack trace to the current line, so the method where the error actually happened disappears from the trace. Use throw;, or wrap it: throw new MyException("context", ex);.

What is an exception filter in C#?

A when clause after a catch (C# 6 and later): catch (HttpRequestException e) when (e.Message.Contains("404")). The catch block runs only if the condition is true; otherwise the exception keeps looking for another handler as if the clause were not there, and its stack is not unwound.

Should I catch Exception in C#?

Only at the edges of a program: the top of Main, a request handler, or a background loop, where the job is to log the error and keep going or exit cleanly. Deep in the code, catch the specific types you can actually handle, and let everything else propagate.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED