开发者

What is the purpose of throwing specific exception subclasses?

开发者 https://www.devze.com 2023-01-21 04:06 出处:网络
Why is it preferable to throw this Exception Throw New DivideByZeroException(\"You can\'t divide by zero\")

Why is it preferable to throw this Exception

Throw New DivideByZeroException("You can't divide by zero")

over this general one:

Throw New Exception开发者_运维问答("You can't divide by zero")

What advantage is gained in this particular example? The message already tell it all. Do standard subclasses that inherit from the base Exception class ever have different methods that the base? I haven't seen a case, but I must admit that I tend to throw the base Exception.


The type of the exception allows handlers of the exception to filter it. If all you threw were exceptions of type Exception how would handlers know what exceptions to catch and which to allow to propagate up the call stack?

For example, if you always throw Exception:

void Foo(string item) {
  try {
    if (Bar(item)) { 
      Console.WriteLine("BAR!");
    }
  } catch (Exception e) {
    Console.WriteLine("Something bad?");
  }
}

bool Bar(string item) {
  if (item == null) {
    throw new Exception("Argument is null!");
  }

  return Int32.Parse(item) != 0;
}

How does the caller Foo know if a null exception occurred or if the Int32.Parse() failed? It has to check the type of the thrown exception (or do some nasty string comparison).

It's even more worrisome if you get a ThreadAbortException or OutOfMemoryException which can occur in spots you wouldn't expect an exception. In these cases if your catching code only catches Exception you may mask these (important) exceptions and cause damage to your program (or system) state.

The example code should read:

void Foo(string item) {
  try {
    if (Bar(item)) { 
      Console.WriteLine("BAR!");
    }
  } catch (ArgumentNullException ae) {
    Console.WriteLine("Null strings cannot be passed!");
  } catch (FormatException fe) {
    Console.WriteLine("Please enter a valid integer!");
  }
}

bool Bar(string item) {
  if (item == null) {
    throw new ArgumentNullException("item");
  }

  return Int32.Parse(item) != 0;
}


Because you can have multiple catch statements and handle different errors differently.

For example, a DivideByZero exception might prompt the user to correct an entry, while a FileNotFound exception might alert a user that the program can't continue and close the program.

There's a nice in-depth article answering this question here: Link


Rather than filtering based on the text send along the error stream, you can catch multiple types of exceptions. Each one may have a very specific way to perform a recover. The text is just there to provide the user or debugger some feedback, but the program cares about the exception type. For the same reason there is polymorphism for user created classes, there is for exceptions.

It is much easier to include multiple catch statements for different exception types than it is to parse the message text to understand what needs to be done to correctly handle the issue.


Directly from MSDN - Exception Handling:

Consider catching specific exceptions when you understand why it will be thrown in a given context.

You should catch only those exceptions that you can recover from. For example, a FileNotFoundException that results from an attempt to open a non-existent file can be handled by an application because it can communicate the problem to the user and allow the user to specify a different file name or create the file. A request to open a file that generates an ExecutionEngineException should not be handled because the underlying cause of the exception cannot be known with any degree of certainty, and the application cannot ensure that it is safe to continue executing.

Do not overuse catch, as throwing another exception from within a catch block will reset the stack trace and cause the lost of important debugging information, as once again MSDN suggests:

Do not overuse catch. Exceptions should often be allowed to propagate up the call stack.

Catching exceptions that you cannot legitimately handle hides critical debugging information.

In the end, catching an exception should be for handling specific exceptions that you expect to occur under certain common scenario where you would like to log or to have some specific behaviour upon exception catch, otherwise simply throw it away, as Eric Lippert himself recommends on his blog (see Too much reuse article).

try {
    ...
} catch (Exception ex) {
    throw; // This does not reset the stack trace.
}

Instead of:

try {
    ...
} catch (Exception ex) {
    throw ex; // This does reset the stack trace.
}

Finally, an Exception does not obligatory need to offer some specificities as supplemental properties or methods or whatsoever, it is the name of it that speaks out for itself, allowing you to filter your catch upon a specific type of exception.

EDIT #1

Another interesting link about error handling on Eric Lippert's blog: Vexing exceptions.


The various subclasses of Exception carry semantic meaning - an ArgumentNullException indicates a different problem than one that generates a DivideByZeroException, and the programmer can handle these problems differently. Additionally, subclasses may define extra properties or methods that can assist in diagnosing or handling the problem, if the programmer chooses to use them.


An exception is typically either (1) caught, logged, and re-thrown, (2) caught and handled, or (3) not caught.

If it is caught, logged and re-thrown then there could be important extra information stashed away in a specific exception type that allows the logging code to dump more rich information, so that the analyst who is trying to debug the problem that caused the exception can do so more efficiently.

If it is caught and handled then you need to know how to handle the problem. If an exception is of a specific type then that is a big clue to the developer who is writing the handler about whether they can handle the exception or not. You should only handle exceptions that you expect and know how to recover from.

If it is not caught then the process is going to go down and probably a crash dump is going to be sent somewhere. Now we're back in case (1).

In all three cases having more type information is preferable to having less.


try {
    Do();
}
catch (MyException)
{
    // reaction on MyException
}
catch (AnotherException)
{
    // another reaction on AnotherException
{
// SomeException will not be caught


void Do()
{
    if (...)
        throw new MyException();
    else if (...)
        throw new AnotherException();
    else
        throw new SomeException();
}


In a well-designed exception hierarchy, having different types of exceptions makes it possible to have catch statements which take different actions in different circumstances. Ideally, one family of exceptions would emerge from a function if the particular action couldn't be completed, but class invariants which should have been applicable before the action was attempted likely still hold, with the sole exception of those implied by the action's failure. For example, a collection's get-object method should throw an exception from this family if the collection seems valid but the requested object doesn't exist.

If a function fails in such a way that indicates that class invariants did not hold when it was called, or no longer hold after it returns, it should throw an exception from a different family. Note that it may be appropriate for a function to catch an exception from the first family and rethrow as the second, if the only way the exception would have occurred would be if invariants were violated. It may also be appropriate on occasion to catch an exception of the second type and throw one of the first, if the exceptions came from an object which never going to be used after the function returns.

If things are really dire (e.g. OutOfMemoryException, CpuCatchingFireException, etc.) that should be another hierarchy separate from the first two.

Existing exceptions don't follow that pattern, but one could use it for any new exceptions one creates.

0

精彩评论

暂无评论...
验证码 换一张
取 消