Preliminary note:
This question is not intended to bash on .NET, nor is intended to wage a discussing war if there is such a thing as a "Fatal Exception" - Java's designers clearly thought there are, .NET designers either thought otherwise, didn't know otherwise, or there may be another (technical) reason for the exception hierarchy being the way it is.
I'm mostly interested in whether there's a design document or statement by any of the MS designers why the .NET hierarchy is the way it is today. Wild guesses and speculation will make bad answers. Cohesive arguments/examples why there is No Such Thing as a (Categorizable) Fatal Exception certainly can make valid answers, although I'm bound to disagree with them.But, as opposed to comments, I promise not to argue with any cohesive answer ;-) Another category of answers could show evidence that Java's Error
type categorization is a bad idea / doesn't work in practise in Java, thereby implicitly showing why .NET didn't need it. :-)
While working my way through to becoming a more experienced C# programmer, I have noticed something that I consider rather strange(*) in the .NET exception hierarchy:
The base class for all thrown types is Exception
(well, basically anyway).
Specifically, many exceptions directly derive from Exception
and the further categorization into SystemException
-> etc. seems rather pointless a bit arbitrary.
As an example, especially strange to me seems that an SEHException
is-a ExternalExpection
is-a SystemException
when it really would seem to be more like a crash-and-burn kind of error.
While not overly experienced in Java, I find the distinction Java makes wrt. the Error
type vs "normal" Exception
to make a lot of sense. Details can certainly be argued, but .NET
/C# not even trying such an approach seems strange.
Eric Lippert of C# fame has a nice piece on categorizing exceptions which I mostly agree with and which makes me wonder even more why .NET does not even try to provide a bucket for "Fatal Exceptions".
By "Fatal Exception" I basically allude to the same concept Mr. Lippert describes:
Fatal exceptions are not your fault, you cannot prevent them, and you cannot sensibly clean up from them. ...
Note: Most importantly, they are likely and specifically also not the fault of the operation you called that raised the exception.
... They almost always happen because the process is deeply diseased and is about to be put out of its misery. Out of memory, thread aborted, and so on. There is absolutely no point in catching these because nothing your puny user code can do will fix the problem. Just let your "finally" blocks run and hope for the best. (Or, if you're really worried, fail fast and do not let the finally blocks run; at this point, they might just make things worse. But that's a topic for another day.)
I will note that it is totally OK for some set of fatal exception to technically be just like any other exception. You should very well be able to catch it at appropriate times - it's just that for 99% of code it not appropriate to handle these at all.
Take Mr. Lipperts examples: Say I call a function to open a file that can raise various exceptions. If I catch any of these, all I want to do is report that opening the file failed with reason X and continue appropriately. However if opening a file raises, say, a ThreadAbortedException, it doesn't make sense to report anything as not the file open operation failed, but some code aborted the current thread and handling that at the same point as the hypothetical FileNotFoundException doesn't make sense in the vast majority of cases.
Commenters seem to think that only the catch site can really judge whether something is "fatal" and no prior categorization is appropriate, however I would strongly suspect that there is a good list of exceptions that 99% of user code never wishes to catch:
Take StackOverflowException
(and I'm sure there's more) which is "just" a regular SystemException that is hardwired to be fatal:
In the .NET Framework 1.0 and 1.1, you could catch a StackOverflowException object (for example, to recover from unbounded recursion). Starting with the .NET Framework 2.0, you can’t catch a StackOverflowException object with a try/catch block, and the corresponding process is terminated by default.
Note that I do not think a fatal exception must terminate the application immediately (on the contrary). All I ask is why the .NET framework doesn't try to represent "fatalness" in the hierarchy of the exceptions, when the designers clearly do consider some exception more fatal than others.
(*) "consider rather strange" actually means that I, personally, at this very point in the time continuum, find the .NET exception hierarchy totally botched.
In computing, a fatal exception error or fatal error is an error that causes a program to abort and may therefore return the user to the operating system. When this happens, data that the program was processing may be lost.
There is no such thing as a "fatal" error, as distinguished from a "non-fatal" error; also, exceptions can be fatal. It just depends upon how they are handled (or not) as to whether that error/exception causes termination of the JVM. Note that errors generally should not be caught; some exceptions should be.
The error indicates trouble that primarily occurs due to the scarcity of system resources. The exceptions are the issues that can appear at runtime and compile time. 2. It is not possible to recover from an error.
Most importantly don't fail silently so don't wrap the exceptions in your own exception and then ignore them. Better to allow them to bubble up and find them in the web server log for example.
First of all let me say that this is not a particularly good question for StackOverflow. It is not a specific technical question about real code, but rather seeks either a design document -- an off-site resource -- or a justification for why a particular class hierarchy was designed the way it was. Neither are really good fit for SO.
That said, I can make a few observations here.
I think it is a reasonable supposition that everyone involved regrets the existence of SystemException
and ApplicationException
. It seemed like a good idea at the time to divide exceptions into two broad categories: exceptions generated by "the system" itself, and exceptions created by users of the system. But in practice, this is not a useful distinction because the thing you do with exceptions is catch them, and under what circumstances do you want to catch either (1) all user-generated exceptions or (2) all system-generated exceptions? No circumstances come to mind.
What this illustrates here is not so much a failure of design in the specific case -- though surely the design is not great -- but rather that in single-inheritance OOP you get only one shot at the "inheritance pivot", as it is often called at Microsoft, and a poor decision can stick with you for a long time.
Now it is very easy to say that in retrospect, we should have maybe used some other pivot. You note that in my article I classify exceptions according to how they are to be caught -- fatal exceptions are not caught because catching them does you no good, boneheaded exceptions are not caught because they are actually debugging aids, vexing exceptions have to be caught because of bad API design, and exogenous exceptions have to be caught because they indicate that the world is different than you'd hoped. It seems like there is the possibility of a better pivot here, where the exception type indicates whether it is fatal or not, and indicates whether it needs to be caught or not. This is of course not the only possible pivot, but it seems plausible. One could design a static analyzer (either in the compiler or a third-party tool) that verifies that the exception is being caught correctly.
It seems particularly plausible because of course there are some exceptions in .NET that are effectively super-duper-fatal. You can catch a stack overflow or a thread abort, but the system is aggressive about re-throwing them. It would be nice if that were captured in metadata somehow.
As a language designer though I would take that retrospection farther, to say that the fundamental problem here is that the exception mechanism itself is being overworked if not abused.
For example, why do we need fatal exceptions to be exceptions? If it really is the case that some exceptions are fatal, and it really is the case that in some rare but crucial scenarios, you need code to run even when a program is going down due to a fatal exception, then that scenario might rise to the level where you want syntax in the language to capture those semantics. Say a try-fatality
block where the cleanup code only runs in the extraordinarily unlikely case of a fatal fault. Maybe that's a good idea, maybe it's not, but the point is that it is an idea about putting a feature in the language to solve a problem rather than piling more and more semantics onto a mechanism that seems perhaps not ideally suited to all the uses that it is put to.
For example, why are there "boneheaded" exceptions at all? The existence of boneheaded exceptions like "this reference isn't allowed to be null, you dummy", or "you tried to write to a file after closing it, you dummy", are in fact only exceptions because they are shortcomings of, in the first case the type system and in the second case, the API design. Ideally there would be no boneheaded exceptions because it would simply not be possible to represent the bad program in the language in the first place. Mechanisms like code contracts could be built into the language that help ensure that a "boneheaded" exception situation is caught at compile time. Again, maybe this is a good idea or maybe not, but the point is that there is an opportunity to solve this problem at the language design level, rather than making exceptions carry that burden, and then asking how to design a hierarchy of types that represents a boneheaded exception clearly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With