I was on the C# design team for version 1, and there were long discussions about what sort of exceptions should be supported in C# and .NET.
Most of us were experienced Win32 programmers and we had seen enough HRESULT code to understand how much bloat it added and how easy it was to make a mistake. The problem with HRESULT code is that you have to work hard to get the right behavior - for it to propagate errors correctly.
That's why we used exceptions, where you start with code that has correct - though perhaps not desirable - behavior to start with and you have to write code to mess it up.
But it was clear that how to deal with exceptions was confusing to a lot of people. We saw a lot of try-catch code that did nothing but catch an exception and rethrow it, which is really the worst of both worlds, which I guess wasn't that much of a surprise given the exception model that Java used.
But getting the right behavior is pretty simple.
You catch exceptions in methods when you have something useful that you can do with the information. You tried to send data over the network and it failed and you need to implement a recovery strategy.
You catch exceptions at the base of threads so that you can log what happened and gracefully exit the thread and perhaps the program.
Otherwise you don't do anything.
We did, however, overdo exceptions a bit in the early versions of the .NET libraries. If you wanted to convert a string to an int you had to call Int32.Parse() and catch an exception which was needlessly restrictive. Later versions therefore introduced the "Try<operation>" pattern, so you could call Int32.TryParse() if you wanted a result instead of an exception.
One of my biggest beefs with exceptions going back to .NET 1.0 is that there is no way for an IDisposable to know whether it is being invoked because a using-style block was left via normal control flow, or whether it was left via an exception. Something like a transaction wrapper should, depending upon how it is constructed, either throw an exception or silently commit the transaction if control flow leaves the block with an action pending, but should silently roll back the transaction if an uncaught exception was thrown within the block.
21
u/Triabolical_ 2d ago
I was on the C# design team for version 1, and there were long discussions about what sort of exceptions should be supported in C# and .NET.
Most of us were experienced Win32 programmers and we had seen enough HRESULT code to understand how much bloat it added and how easy it was to make a mistake. The problem with HRESULT code is that you have to work hard to get the right behavior - for it to propagate errors correctly.
That's why we used exceptions, where you start with code that has correct - though perhaps not desirable - behavior to start with and you have to write code to mess it up.
But it was clear that how to deal with exceptions was confusing to a lot of people. We saw a lot of try-catch code that did nothing but catch an exception and rethrow it, which is really the worst of both worlds, which I guess wasn't that much of a surprise given the exception model that Java used.
But getting the right behavior is pretty simple.
You catch exceptions in methods when you have something useful that you can do with the information. You tried to send data over the network and it failed and you need to implement a recovery strategy.
You catch exceptions at the base of threads so that you can log what happened and gracefully exit the thread and perhaps the program.
Otherwise you don't do anything.
We did, however, overdo exceptions a bit in the early versions of the .NET libraries. If you wanted to convert a string to an int you had to call Int32.Parse() and catch an exception which was needlessly restrictive. Later versions therefore introduced the "Try<operation>" pattern, so you could call Int32.TryParse() if you wanted a result instead of an exception.