The thing that I think the writer missed is that CLR (and Java) exceptions serve two totally different design patterns w.r.t. error handling.
You see, CLR exceptions solve both the "how do I report an error" problem, AND the "what information should be contained in my error report" problem. The first part of the solution has to do with the asynchronous nature of exceptions - any statement can potentially throw an exception, and a caller is expected to catch the exception. The second part is about what information is carried along with the error information.
IMHO, the System.Exception object is just another kind of error code object - it's functionally equivalent to an HRESULT combined with the IErrorInfo interface. It's job is to provide sufficient context to the caller that the caller can determine some kind of reasonable behavior based on the error.
In fact, you could almost consider an exception hierarchy based off of System.Exception as a modern implementation of an X.400/x.500 OM error structure (X.400/X.500 OM errors are complex nested structures that described the source of the error, suggested recovery modes, etc).
The interesting thing about x.400/x.500 error codes is that they were sufficiently complicated that they were almost completely unusable. Most people who manipulated them took the highly complex data structure and mapped it to a simple error code and operated off of that error code. Why? Because it was simpler - checking against a particular error code number was far easier than parsing the OM_error structure.
The good news for the "System.Exception as an uber error code" is that it's relatively easy to determine what kind of error failed from the strong type information that the CLR provides, which means that the "deconstruct the rich information into a simpler version" pattern I just mentioned isn't likely to happen.
But you should never believe that "exceptions" somehow solve the "how do I return sufficient information to the caller of my API" problem - exceptions per se do not, even though an object hierarchy derived from System.Exception has the potential of solving it. But the "throw an exception to handle your errors" design pattern doesn't.
As a trivial counter example, consider the following C++ class (I'm just typing this in, don't expect this to work):
HANDLE Open(LPCWSTR FileName)
fileHandle = CreateFile(FileName, xxxx);
if (fileHandle == INVALID_HANDLE_VALUE)
DWORD OpenError(LPCWSTR FileName, OUT HANDLE *FileHandle)
*FileHandle = CreateFile(FileName, xxxx);
if (&FileHandle == INVALID_HANDLE_VALUE)
This rather poorly implemented class has two methods. One of them uses exception handling for error propagation, the other returns the error code. The thing is that as far as being able to determine corrective action, the two functions are totally equivalent. Neither of them give the caller any information about what to do about the error.
So claiming that the "throw an exception as a way of reporting an error" paradigm somehow solves the "what should I do with this error" problem is naive.