I just got finished debugging a crazy problem with some C# code. This particular code is part of an application that deals with file spooling, and it deals with alot of ambiguous file locking issues by always locking greedily. Any spooling task that is unable to gain an exclusive lock on a file simply assumes another thread must be working on the file and it leaves it alone.
The developer used a pattern that handles System.IO.IOException and assumes that any IOException must be because of a concurrency issue. The problem with this is that there are lots of calls that can cause file IO that you may be unaware of. In this case, there was an assembly binding issue caused by some plugin-style dynamic loading that was throwing System.IO.FileLoadException (Which actually seems to be out of place as a subclass of IOException since it is specific to assembly loading and not IO in general). The pattern in the code was assuming that, in general, any IOException was not an exceptional event and signified another condition. So, the task never did any work and never reported the exceptional condition.
Eric Gunnerson wrote a nice overview piece on exceptions in C# on msdn. Some of his guidelines are
Catch the most specific exception
If your code needs to recover from some exceptions, make sure to catch only those exceptions. If you catch more general ones, it's more likely you'll mistakenly swallow exceptions you don't want to swallow.
Only swallow if you're sure
This is really a corollary of the previous guideline. When you swallow an exception, your saying that you understand all cases where this exception could arise, and that the recovery code you're writing handles all of those cases.
Use lock or using if applicable
If you can use the lock or using statements, use them. They make the code more readable and make it more likely you'll do the right thing.
Wrap exceptions if applicable
If you can add additional information to an exception, by all means do so. If I pass a parameter on to another function, it might be useful for me to add additional information about the parameter.
The first two here are obviously directly applicable to the scenario, and would have at least raised some flags if I had first checked all the IOExceptions to see what they encompass. The solution for me was to take a look at the pattern and reduce the scope of the IOException catches to only those statements that I expected might throw the exception for locking.
[UPDATE] I wanted to note that the exception handling pattern worked great until it was extended by me to a more complicated scenario involving dynamic assembly loading.