In .NET, putting a try block around code is cheap, but exceptions are very expensive to throw. This is largely because of the rich state that .NET exceptions contain. Exceptions must be reserved for truly exceptional situations, when raw performance ceases to be important.
To see the devastating effects on performance that throwing exceptions can have, see the ExceptionCost sample project. Its output should be similar to the following:
Empty Method: 1x Exception (depth = 1): 8525.1x Exception (depth = 2): 8889.1x Exception (depth = 3): 8953.2x Exception (depth = 4): 9261.9x Exception (depth = 5): 11025.2x Exception (depth = 6): 12732.8x Exception (depth = 7): 10853.4x Exception (depth = 8): 10337.8x Exception (depth = 9): 11216.2x Exception (depth = 10): 10983.8x Exception (catchlist, depth = 1): 9021.9x Exception (catchlist, depth = 2): 9475.9x Exception (catchlist, depth = 3): 9406.7x Exception (catchlist, depth = 4): 9680.5x Exception (catchlist, depth = 5): 9884.9x Exception (catchlist, depth = 6): 10114.6x Exception (catchlist, depth = 7): 10530.2x Exception (catchlist, depth = 8): 10557.0x Exception (catchlist, depth = 9): 11444.0x Exception (catchlist, depth = 10): 11256.9x
This demonstrates three simple facts:
- A method that throws an exception is thousands of time slower than a simple empty method.
- The deeper the stack for the thrown exception, the slower it gets (though it is already so slow, it doesn’t matter).
- Having multiple catch statements has a slight but significant effect as the right one needs to be found.
On the flip side, while catching exceptions may be cheap, accessing the StackTrace property on an Exception object can be very expensive as it reconstructs the stack from pointers and translates it into readable text. In a high-performance application, you may want to make logging of these stack traces optional through configuration and use it only when needed.
To reiterate: exceptions should be truly exceptional. Using them as a matter of course can destroy your performance.