Overhead of try/finally in C?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
The try/finally construct in C# guarantees that cleanup code in the finally block executes regardless of whether an exception is thrown. Developers sometimes worry about the performance cost of wrapping code in try/finally, especially in hot loops or performance-critical paths. In practice, the .NET JIT compiler applies optimizations that make the overhead negligible when no exception is thrown.
How try/finally Works at the JIT Level
When the JIT compiler encounters a try/finally block, it generates metadata that maps the protected region of code to the corresponding finally handler. During normal execution, the CPU runs through the try block instructions without any additional branching or checking. The runtime only consults the exception-handling tables when an exception actually occurs.
In the happy path where no exception is thrown, the generated machine code for the try block is nearly identical to what it would be without the try/finally wrapper. The JIT still inlines methods, eliminates bounds checks, and applies other optimizations within the try block.
Measuring the Overhead with BenchmarkDotNet
You can measure the actual overhead using BenchmarkDotNet to compare a method with and without try/finally.
In typical results, the two methods produce nearly identical timings. The difference is within the margin of measurement noise, confirming that try/finally with no exception has effectively zero runtime cost.
Where try/finally Does Have Cost
The overhead becomes real in two scenarios. First, when an exception is actually thrown, the runtime must walk the stack, find the matching handler, and execute the finally block. This process is orders of magnitude slower than normal execution.
Second, the presence of a try/finally block can inhibit certain JIT optimizations. For example, the JIT may not inline a method that contains exception-handling constructs, and it may limit register allocation optimizations within the protected region.
In modern .NET (6 and later), the JIT has improved significantly and can inline methods with simple try/finally blocks in many cases.
try/finally vs the using Statement
The using statement in C# compiles down to a try/finally block. They have identical performance characteristics.
The using declaration syntax introduced in C# 8 also generates the same try/finally pattern, with the finally block at the end of the enclosing scope.
Since using is just syntactic sugar over try/finally, there is no reason to avoid using for performance reasons.
When the Overhead Matters
In the vast majority of applications, the overhead of try/finally is not measurable. However, there are narrow scenarios where you should be aware of it.
In micro-benchmarks running billions of iterations, placing try/finally inside a tight loop rather than outside it can show a small but consistent difference because it may affect the JIT's ability to optimize the loop body. The solution is to structure your code so that try/finally wraps the loop rather than being inside it.
Common Pitfalls
- Avoiding try/finally for performance reasons: The overhead on the happy path is effectively zero. Skipping
try/finallyto save performance leads to resource leaks that cause far worse problems than any micro-optimization could solve. - Throwing exceptions in normal control flow: The real cost of exception handling comes from throwing and catching exceptions, not from the
try/finallystructure itself. Never use exceptions for expected control flow like loop termination or validation. - Placing try/finally inside tight loops: While the overhead is small, placing a
try/finallyinside a loop that runs millions of times can prevent loop optimizations. Move thetry/finallyoutside the loop when possible. - Assuming using is slower than manual cleanup: The
usingstatement compiles to the sametry/finallypattern. ManualDispose()calls withoutusingrisk missing cleanup on exceptions and offer no performance benefit. - Ignoring JIT version differences: Older .NET Framework JIT compilers are more conservative about optimizing around
try/finallythan modern .NET JIT. If you are targeting .NET Framework 4.x, test performance-critical paths with benchmarks rather than assuming modern JIT behavior.
Summary
- The
try/finallyconstruct has effectively zero overhead when no exception is thrown because the JIT generates normal code with exception-handler metadata consulted only on exceptions. - The real cost comes from actually throwing exceptions, which involves stack walking and handler lookup.
- The
usingstatement compiles totry/finallyand has identical performance characteristics. - Place
try/finallyoutside tight loops rather than inside them to avoid inhibiting loop optimizations. - Always prefer correct resource cleanup with
try/finallyorusingover micro-optimizations that risk resource leaks.

