I've seen so many ways that exceptions are misused in .NET code when it really should be so easy. I will lay out some recommendations that I have come up with through dealing with poorly written code and from reading everything I can find about exception handling in .NET. I readily admit that these recommendations are opinionated, but I feel like I have earned the right to my opinions from beating my head against the wall as I keep running into these issues.
The first rule of exception handling in .NET is DON'T! Unless you can fix the problem (hint: usually, you can't), just let the exception bubble up to the top layer of the app. Do not log the exception at this level. It gives you a false sense of having 'handled' it.
The second rule of exception handling is use a global exception handler (like ELMAH for ASP.NET, or your own exception handling class) to log the exception and notify the user. This should happen at the topmost level of the application.
I see this one all the time, and I can't understand it at all. Some people will do a try/catch and then re-throw the exception. Not only is this completely unnecessary; it is actively harmful. In a debugging scenario, execution will break where you catch it, not where the problem originally occurred making it that much more difficult to track down the problem.
Do not create your own exception types. I have never seen this done where it did not add complexity and obfuscate the real issue. Yes, it's theoretically possible to do it well, but resist the urge to try. Your fellow programmers will thank you.
NEVER swallow exceptions. NEVER, EVER, EVER. Unless you have thought very seriously about ALL the possible consequences. Even then, tell yourself, "I'm probably making a mistake."
I suppose a word about throwing exceptions is in order.
First, apply the Samurai Principle - return successfully or not at all. If your method cannot do what its name says, throw an exception. Ironically, there are a couple of exceptions to this rule. When implementing either the TryParse or TryGet pattern or the NullObject pattern, you would return either a boolean or the NullObject.
Next, forget that soundbite: "Exceptions are for exceptional situations." I personally heard Jeff Richter say, "that's the stupidest thing I ever heard." He went on to explain that it's stupid if you take it in the sense of frequency. What it really means is that it is an exception to the rule. What rule? Well, whatever your method name is. Your method name is a contract. It says, "if you give me what I ask for (parameters), I will do whatever the method name is." If your method cannot complete its contract, throw an exception.
The first thing to do in your method is any kind of check or validation of the method's arguments. If something is wrong, fix it or throw an exception.
Always throw the most specific exception type that accurately represents what happened.
Now, if everybody will adopt my recommendations, it will make my life considerably easier. :)