6

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.

Community
  • 1
  • 1
Martin Ba
  • 37,187
  • 33
  • 183
  • 337
  • 5
    My opinion: the thrower can not know if an exception is "fatal". Only the catcher. – Cory Nelson Jan 07 '15 at 23:03
  • 1
    IMHO, Cory's answer is correct - "Fatal" is in the eye of the beholder. – John Saunders Jan 07 '15 at 23:06
  • `SEHException` is useful for interop scenarios. It's **not** a fatal exception. – Lucas Trzesniewski Jan 07 '15 at 23:07
  • 1
    @LucasTrzesniewski - when you get an SEHException it has already passed through several layers of native code that was - in all likelyhood - *not* prepared to handle it (see [`/EH` modes of MSVC](http://msdn.microsoft.com/en-us/library/1deeycx5.aspx)). If you catch an SEHException and continue, you are extremely likely to mess up. (Of course, there are counter examples, but I'd argue they are rare.) – Martin Ba Jan 07 '15 at 23:18
  • @Martin I was thinking about P/Invoking a C++ dll that throws a SEH exception you'd want to catch from C#. Yeah, weird scenario, probably not the best idea either, but at least it's possible. – Lucas Trzesniewski Jan 07 '15 at 23:22
  • @LucasTrzesniewski - I'm not per se against the possibility of catching an SEHException, I just find it weird to treat it like any other exception, when it will most likely just be the result of a bug in some native code part. – Martin Ba Jan 07 '15 at 23:36
  • I just think you're flat-out wrong. "Fatal is in the eye of the beholder", period. If there are a subset of exceptions that most beholders consider fatal, then that still doesn't mean we need some characterization of these exceptions as "fatal", nor does it mean we need some common "fatal" behavior for these exceptions. Although you don't state in your question what the common behavior of "fatal" exceptions should be, your question implies that you think they should all "crash the application" or something. – John Saunders Jan 07 '15 at 23:43
  • Any exception that goes unhandled is fatal. If an exception can be mitigated and allow the program to continue to run, then it should be caught and handled appropriately. This is an orthogonal concept to what type of exception it is. Any exception *can* be handled, but usually you should only write code to handle exceptions that you can reliable recover from. Thus where that very informative(sarcasm) error message "unhandled exception has occurred" comes from: errors that bubbled all the way out, unhandled, and crashed the program. – AaronLS Jan 07 '15 at 23:45
  • If we accept you proposition that the concept of a "non fatal exception" is valid, then you'd have code that would throw exceptions when something non-critical went wrong, and we should safely be able to ignore such exceptions? Exceptions by their very definition interrupt program flow and transfer control to catch/finally. Therefore, every single function call we'd make, we'd have to anticipate that it *might* throw exceptions which should be treated non-fatal and require us to wrap these calls with try/catch to prevent the non-fatal exception from interupting program flow in the caller. – AaronLS Jan 07 '15 at 23:52
  • That said, I think there is potential for a good non-opinion based answer. To say there should be a fatal error type implies there should be a non-fatal type, but then that begins to waffle down the road of errors which can be ignored by default, which is the same problem where a function's return indicates errors, which is exactly the very problem exceptions are intended to solve. The reasons are concrete. The only thing that might be opinion based is whether you believe that rational to be sufficient to warrant the design. – AaronLS Jan 08 '15 at 00:03
  • @AaronLS - Big Misunderstanding. See edit. I do not propose to ignore non-fatal exceptions. On the contrary. I ask why it's not made easy to *not* catch fatal exceptions, thereby making it easier to catch the recoverable/"normal" ones. – Martin Ba Jan 08 '15 at 00:14
  • @JohnSaunders - I have added an explanation of "fatal exceptions". I want them to be normal exception - no forced termination or anything. It's just I disagree with you that "Fatal" is totally in the eye of the beholder. A reasonable categorization would be possible. (see StackoverflowException, see SEHException when native code uses /EHSc, see majority of OutOfMemory exceptions, ...) – Martin Ba Jan 08 '15 at 00:19
  • You can catch and recover from `StackOverflowException`, if user code is the one throwing it :) But I guess that's not the case you're discussing. – MarcinJuraszek Jan 08 '15 at 00:23
  • @MartinBa I would word the question in terms of exceptions that cannot be handled by user code, versus those that can. I think your use of the terminology "fatal" is misleading to what you are trying to express and should be eliminated from your question. Perhaps "recoverable exceptions" vs. "non-recoverable exceptions" since this is the terminology used in documentation describing the stack overflow exception. – AaronLS Jan 08 '15 at 00:38
  • @AaronLS - thanks. However, I do think that "fatal" is a good word here. It's certainly used by Mr. Lippert in his post in mostly exactly the sense I mean it in the question. I'm not sure "non-recoverable" is a good term either, as these exception are certainly recoverable *sometimes*, they just should be separate from those exceptions that can meaningfully be handled most of the time. – Martin Ba Jan 08 '15 at 00:47
  • @MartinBa Eric's use of "Fatal" here is among four made-up exception categories: boneheaded, vexing, and exogenous. These are categories of his own invention simply to convey his thoughts on proper exception handling, and I don't fault him for it as such constructs are sometimes handy in communicating abstract ideas. If you look back at our comments though, you'll see that when you use the term "fatal exception" it means something very different than one you intended to the majority of us. – AaronLS Jan 08 '15 at 00:58
  • 1
    @MartinBa Note that his discourse on how to handle exceptions hinges little on hierarchy or exception types, but focuses more on the circumstances surrounding the exception. For example, it *seems* he might be suggesting you always catch and ignore exceptions from Parse because he says "and therefore must be caught and handled all the time", but that is not what he is saying at all. The first part of the sentence "Vexing exceptions are thrown in a completely non-exceptional circumstance," emphasizes it is the circumstance under which the exception was raised, **not the type** of exception. – AaronLS Jan 08 '15 at 01:02
  • 1
    The exception system in .net was not thought through at the beginning, and later it could not be changed because of the backward compatibility. That's why it's the mess it is. This by the way is a know fact, for example see Jeffrey Richter CLR via C#, if you need more specifics I can lookup the page number once I'm home – Andrew Savinykh Jan 08 '15 at 01:03
  • You also don't say why you think the .NET exception hierarchy is botched. If you explained that, you would need to explain what you think an exception hierarchy should be used for. I think you may not understand how that hierarchy is used in .NET. The answer is that `Exception` is a class, and derived classes are used in the same manner as any derived class - an instance of a derived class can be treated as an instance of the base class. It is not for the purpose of categorization, as _no_ .NET class hierarchy is about any kind of characterization. – John Saunders Jan 08 '15 at 01:03
  • The nature of these comments proves why this question should remain closed. It is an interesting discussion, but it is a _discussion_ on a Q&A site. – John Saunders Jan 08 '15 at 01:04
  • @JohnSaunders, yep, I agree, this is not a good fit for this site. All questions of the type "why designers designed it this way" are not. Unless answered by the designers themselves or someone privy, the answers are all opinion based. – Andrew Savinykh Jan 08 '15 at 01:05
  • You should also either remove "C#" from your title, or else say why you think that answers to this question should differ based on which language is used to access the .NET Framework. – John Saunders Jan 08 '15 at 01:05
  • @JohnSaunders - I do appreciate your feedback on this. We just seem to be in disagreement on several items here and I don't which to clutter up the comment thread more. – Martin Ba Jan 08 '15 at 01:11
  • @zespri If the question were well defined, you could answer "why is it designed" without being the designer, because the practices are usually familiar from other languages and there is usually a reason for the design. While some designs may be arbitrary, many are intentional and have concrete rational: http://stackoverflow.com/a/10102535/84206 and sometimes you can answer and cite the actual designer if you feel that is really important: http://stackoverflow.com/a/12454932/84206 – AaronLS Jan 08 '15 at 01:33
  • @zespri - Info appreciated, and certainly look up that page number. A for *"... That's why it's the mess it is. This by the way is a know fact ..."* - huh?!? What known fact? Most I find is happy unicorn posts and descriptions laying out how The One True Way to handle - or not - exceptions in C#. I do agree it's a mess, but you seem to be first other person I discover publicly thinking the same :-) – Martin Ba Jan 08 '15 at 02:02

1 Answers1

16

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.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Thanks for taking the trouble to share your thoughts, even though I agree the question is a bad fit according to SO rules. Your points are all valid, but one thing that would *really* help in C# (and C++ for that matter) IMHO is being able to catch multiple exceptions in one catch handler. – Martin Ba Jan 12 '15 at 09:31
  • 5
    You have no idea how disappointed I now am that `try { Fight(); } fatality { FinishHim(); }` is not valid C#. – Cole Campbell Jan 13 '15 at 19:44
  • @ColeCampbell: Make a suggestion on the Roslyn forum and maybe the language designers will consider it. :-) Incidentally, the CLR already supports a "fault" block -- that is, a "finally" handler that *only* runs when there is an exception propagating, but does not halt the exception propagation like a "catch" handler does. That could be added to C# without much difficulty. – Eric Lippert Jan 13 '15 at 21:59
  • 3
    Fairly remarkable that nobody has noted that this kind of classification does in fact exist. Called CSEs, Corrupted State Exceptions, the docs for HandleProcessCorruptedStateExceptionsAttribute are useful. – Hans Passant Jan 19 '15 at 07:32
  • @HansPassant - I just recently learned about CSE and one thing that I find odd is that there is no official list of what is considered a CSE, just blog posts. – Martin Ba Jan 30 '15 at 09:53