Are these try/catch'es equivalent?

2020-07-02 09:54发布

问题:

Scenario

I have a method that does database operation (let's say). If during that operation any exception is raised, I just want to throw that exception to the caller. I don't want to do any specific task in the catch block, assuming caller will do whatever it wants to do with that exception. In this scenario, which one is appropriate exception handling technique?

try
{
    // Some work that may generate exception
}
catch(Exception)
{
    throw;
}
finally
{
    // Some final work
}

Is the above equivalent to the following try/catch/finally?

try
{
    // Some work that may generate exception
}
catch
{
    throw;
}
finally
{
    // Some final work
}

Is the above equivalent to the following try/finally?

try
{
    // Some work that may generate exception
}
finally
{
    // Some final work
}

Which one is better than the other? Which one should be used?

回答1:

No, they are not equivalent. They may be equivalent in some cases, but the general answer is no.

Different kinds of catch blocks

catch block with a specified exception type

The following will only catch managed exceptions that inherit from System.Exception and then executes the finally block, which will happen regardless of whether an exception was thrown or not.

try
{
   // Some work that may generate exception
}
catch (Exception)
{
   throw;
}
finally
{
   // Some final work
}

catch block without a specified exception type

The following catch block without a type specifier will also catch non-managed exceptions that are not necessarily represented by a managed System.Exception object, and then executes the finally block, which will happen regardless of whether an exception was thrown or not.

try
{
   // Some work that may generate exception
}
catch
{
   throw;
}
finally
{
   // Some final work
}

finally block without a catch block

If you do not have a catch block at all, your finally will still be executed regardless of whether or not an exception occoured.

try
{
   // Some work that may generate exception
}
finally
{
  // Some final work
}

When are they equivalent?

In case your catch block doesn't specify an exception and only contains the throw; statement, the last two are indeed equivalent. In case you don't care about non-managed exceptions and your catch block only contains the throw; statement, all three can be considered equivalent.

Notes

A note about throw

The following two pieces of code contain a subtle difference. The latter will re-throw the exception, meaning that it will rewrite the exception's stack trace, so these are definitely not equivalent:

catch (Exception e)
{
    throw;
}

And

catch (Exception e)
{
    throw e;
}

In case you use finally with an IDisposable, the following two pieces of code are almost equivalent, but with some subtle differences:

  • When the object is null, the using statement won't give you a NullReferenceException
  • When using the try-finally technique, the variable remains in scope, although it is very discouraged to use any object after it has been disposed. However you can still reassign the variable to something else.

    Something obj = null; try { obj = new Something() // Do something } finally { obj.Dispose(); }

And

using (var obj = new Something())
{
    // Do something
}


回答2:

You have some good answers so far, but there is an interesting difference that they did not mention so far. Consider:

try { ImpersonateAdmin(); DoWork(); } 
finally { RevertImpersonation(); }

vs

try { ImpersonateAdmin(); DoWork(); }
catch { RevertImpersonation(); throw; }
finally { RevertImpersonation(); }

Suppose DoWork throws.

Now the first question at hand is "is there a catch block that can handle this exception", because if the answer is "no" then the behaviour of the program is implementation-defined. The runtime might choose to terminate the process immediately, it might choose to run the finally blocks before it terminates the process, it might choose to start a debugger broken at the point of the unhandled exception, it might choose to do anything it likes. Programs with unhandled exceptions are permitted to do anything.

So the runtime starts looking for a catch block. There's none in this try statement, so it looks up the call stack. Suppose it finds one with an exception filter. It needs to know if the filter will permit the exception to be handled, so the filter runs before impersonation is reverted. If the filter accidentally or deliberately does something that only an admin can do, it will succeed! This might not be what you want.

In the second example, the catch catches the exception immediately, reverts the impersonation, throws, and now the runtime starts looking for a catch block to handle the re-throw. Now if there is a filter it runs after the impersonation is reverted. (And of course the finally then reverts again; I assume that reverting impersonation is idempotent here.)

This is an important difference between these two code snippets. If it is absolutely positively forbidden for any code to see the global state that was messed up by the try and cleaned up by the finally, then you have to catch before finally. "Finally" does not mean "immediately", it means "eventually".



回答3:

Both of the try / catch statements are equivalent in that they are re-throwing the original exception that was caught. However the empty catch is more broad (as Venemo has already stated, catching unmanaged exceptions). If you are to catch an exception and capture it in a variable you can use it for logging or you can throw a new exception while passing the original exception as an argument - making it the "inner exception".

The finally is going to function the same regardless.

Which one should be used, in a scenario where we don't need logging exception and we explicitly assume caller will handle exception being raised like writing in file stream or sending email.

If the caller will handle the exception and you do not need to log the occurrence of the exception at this level, then you should not be catching at all. If the caller will handle an exception being thrown, there is no need to catch an exception just to re-throw it.

Valid reasons to catch an exception that will be re-thrown:

  • throw new Exception("WTF Happened", ex); // Use as inner exception
  • Log exception
  • Use a finally block to execute some cleanup code