I'm really not obsessed with performance -- honest!
However, when a co-worker asked me today exception handling was an acceptable way of coding defensively, my reaction was rather predictable. Exceptions are pure evil, and should be... well, exceptional.
Yes, you guessed it. The next question was "How bad is try/catch really?"
The short answer is that is involves minimal overhead... unless an exception is thrown. In that case, the .NET exception handling mechanism does a few nice things, like providing the stack trace as part of the exception. Great for debugging... lousy for performance. Of course, if exceptions are treated as being exceptional -- that is, they're only raised when something you haven't predicted occurs -- then you really shouldn't care about performance. Since your application is most likely left in an unknown or unstable state after an exception, the odds are you're going to reset a substantial part of your application, if not shut it down entirely.
As a rule, you can predict that users will provide data that is, to put it politely, suspect. This isn't a reflection on the users; if anything, it's more a comment on the tendency of developers to know how their own code works, and to continually feed it good data. But, both for this reason, and for security reasons, the ASP.NET mantra of "never trust any input" applies in pretty much any application. Code defensively against user input, so that you don't have to rely on try/catch blocks to deal with exceptions.
How significant is the overhead of catching an exception? Here's the code for a console application to illustrate:
1: Module Module1
2: Sub Main()
3: TestNaive(1)
4: TestDefensive(1)
5: TestWithTryCatch(1)
6: TestWithException(1)
7:
8: Dim maxCount As Integer = 1000000
9:
10: Console.WriteLine("Naive: " & TestNaive(maxCount) & " ticks")
11: Console.WriteLine("Defensive coding, no try/catch: " & TestDefensive(maxCount) & " ticks")
12: Console.WriteLine("Naive with try/catch: " & TestWithTryCatch(maxCount) & " ticks")
13: Console.WriteLine("Exception: " & TestWithException(maxCount) & " ticks")
14: Console.ReadLine()
15: End Sub
16:
17: Private Function TestNaive(ByVal iterationCount As Integer) As Double
18: Dim sw As New Stopwatch
19: sw.Start()
20: For i As Integer = 1 To iterationCount
21: Dim x As Double = i / (iterationCount - i + 1)
22: Next
23: sw.Stop()
24:
25: Return sw.ElapsedTicks / iterationCount
26: End Function
27:
28: Private Function TestDefensive(ByVal iterationCount As Integer) As Double
29: Dim sw As New Stopwatch
30: sw.Start()
31: For i As Integer = 1 To iterationCount
32: Dim y As Integer = (iterationCount - i + 1)
33: If Not y = 0 Then
34: Dim x As Double = i / y
35: End If
36: Next
37: sw.Stop()
38:
39: Return sw.ElapsedTicks / iterationCount
40: End Function
41:
42: Private Function TestWithTryCatch(ByVal iterationCount As Integer) As Double
43: Dim sw As New Stopwatch
44: sw.Start()
45: For i As Integer = 1 To iterationCount
46: Try
47: Dim x As Double = i / (iterationCount - i + 1)
48: Catch ex As Exception
49:
50: End Try
51: Next
52: sw.Stop()
53:
54: Return sw.ElapsedTicks / iterationCount
55: End Function
56:
57: Private Function TestWithException(ByVal iterationCount As Integer) As Double
58: Dim sw As New Stopwatch
59: sw.Start()
60: For i As Integer = 1 To iterationCount
61: Try
62: Dim x As Double = i / 0
63: Catch ex As Exception
64:
65: End Try
66: Next
67: sw.Stop()
68:
69: Return sw.ElapsedTicks / iterationCount
70: End Function
71: End Module
My results tend to vary a little bit, since the work done in each loop so trivial that it is susceptible to noise. The first three tests are generally pretty close to each other. The fourth test -- testing with an exception raised -- is always much longer. Generally it's somewhat more than 5x longer than the other tests.
What I find really interesting is that the inclusion of the try/catch block appears to make the naive implementation run faster. I don't have the .NET debugging symbols loaded on my machine, and dotTrace really didn't provide any insight. Anyone out there who has dug deeply into the CLR and can provide insight, it would be much appreciated.
However, the moral of the story is straight-forward. Treat exceptions as exceptional. Code defensively rather than catching exceptions wherever possible. No surprise there.