27

I've always thought the difference between "throw" and "throw ex" was that throw alone wasn't resetting the stacktrace of the exception.

Unfortunately, that's not the behavior I'm experiencing ; here is a simple sample reproducing my issue :

using System;
using System.Text;

namespace testthrow2
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                try
                {
                    throw new Exception("line 14");
                }
                catch (Exception)
                {
                    throw; // line 18
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());

            }
            Console.ReadLine();
        }
    }
}

I would expect this code to print a callstack starting at line 14 ; however the callstack starts at line 18. Of course it's no big deal in the sample, but in my real life application, losing the initial error information is quite painful.

Am I missing something obvious? Is there another way to achieve what I want (ie re throwing an exception without losing the stack information?)

I'm using .net 3.5

Community
  • 1
  • 1
Brann
  • 31,689
  • 32
  • 113
  • 162

3 Answers3

37

You should read this article:

In short, throw usually preserves the stack trace of the original thrown exception, but only if the exception didn't occur in the current stack frame (i.e. method).

There is a method PreserveStackTrace (shown in that blog article) that you use that preserves the original stack trace like this:

try
{

}
catch (Exception ex)
{
    PreserveStackTrace(ex);
    throw;
}

But my usual solution is to either simply to not catch and re throw exceptions like this (unless absolutely necessary), or just to always throw new exceptions using the InnerException property to propagate the original exception:

try
{

}
catch (Exception ex)
{
     throw new Exception("Error doing foo", ex);
}
Justin
  • 84,773
  • 49
  • 224
  • 367
  • Thanks, the link you provided was really useful, and calling directly the InternalPreserveStackTrace allowed me to solve my problem ! – Brann Mar 01 '11 at 16:43
  • 2
    In case the link above ever dies, or someone doesn't want to follow it, this is all you need to do to preserve the stack on your exception: `typeof(Exception).GetMethod("InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(ex, null);` – Derek Kalweit Sep 03 '15 at 20:44
11

The problem is that Windows is resetting the stack's starting point. The CLR is behaving as expected—this is just a limitation of the host operating system's exception handling support. The problem is that there can only be one stack frame per method call.

You could extract your exception handling routines into a separate "helper" method, which would work around the limitations imposed by Windows's SEH, but I don't think that's necessarily a good idea.

The proper way to rethrow an exception without losing the stack information is to throw a new exception and include the original, caught exception as the inner exception.

It's difficult to imagine very many cases where you'd really need to do this. If you're not handling the exception, and simply catching it to rethrow it, you probably shouldn't be catching it in the first place.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • ok ; so what's the difference between "throw" and "throw ex" ? – Brann Mar 01 '11 at 09:09
  • see this question for example http://stackoverflow.com/questions/730250/is-there-a-difference-between-throw-and-throw-ex ; this seems to be in contradiction with your answer – Brann Mar 01 '11 at 09:11
  • 1
    @Brann: Uhh, they're still different. `throw ex` resets the stack trace, `throw` doesn't. The problem you've set up here is an edge case. You have the exception handlers in the same method, and Windows only supports one stack frame per method call. The difference between `throw` and `throw ex` is at the level of the CLR; as explained in my answer, that's working correctly here. You're just hitting against a limitation of the host operating system. – Cody Gray - on strike Mar 01 '11 at 09:11
  • Ok I get it. so this would work if wasn't in the same method ; right ? – Brann Mar 01 '11 at 09:12
  • @Brann: Yes, if you split the code into a separate helper method, it would "work". I wouldn't do that just to fix your exception handling routines, though. – Cody Gray - on strike Mar 01 '11 at 09:13
  • @Cody : thanks a lot ; could you please update your initial answer with this ? – Brann Mar 01 '11 at 09:17
  • @Brann: I'm not sure what part I left out of my answer. That you could hack things together by separating them into a two different methods? I suppose I can put that in there, but like I said, I don't think it's necessarily a good idea. – Cody Gray - on strike Mar 01 '11 at 09:19
  • @Cody : well, the common consensus seems to be that throw always preserve the original caller (see the link I posted), and it seems that's in fact only true when the initial exception isn't thrown in the same method as the "throw" statement, so I think it's pretty important to mention it here. thanks. – Brann Mar 01 '11 at 09:28
  • @Brann: I think the first paragraph of my answer already makes an adequate distinction between the CLR's exception handling and the host operating system's (Windows) exception handling. That's the problem with conventional wisdom. I already suggested how to rethrow exceptions the *proper* way, by bundling them as the inner exception in a new exception. Using `throw` is not a magic bullet intended to replace that. Most of the time, it ends up being pointless, because if you can simply rethrow the original exception, **you shouldn't have caught it in the first place.** – Cody Gray - on strike Mar 01 '11 at 09:31
  • @Cody. Well, I need to dispose a shared resource (a connection token) if the code fails. I can't use a finally statement because I don't want to dispose my token if the method succeeds. But in case of a failure, after disposing it, I want my usual error routines to handle/log/etc the error. Doesn't that make sense? – Brann Mar 01 '11 at 09:41
  • @Brann: The idea of having special code which will triggered when an exception happens but won't actually catch the exception is a fine one. In my answer I post a way to do it in C#, and a more powerful way to do it in vb.net (even if you program in C#, it may be useful to have a vb.net wrapper routine if you need to capture the pending exception without "catch"ing it). – supercat Mar 01 '11 at 17:48
3

The normal rethrow preserves everything on the stack trace except that if the present method is in the stack trace, the line number will get overwritten. This is annoying behavior. In C# if one needs to do something in the exceptional case but doesn't care what the exception is, one can use the pattern:

  Boolean ok = False;
  try
  {
    do_something();
    ok = True;
  }
  finally
  {
    if (!ok) // An exception occurred!
      handle_exception();
  }

There are a number where that pattern is very helpful; the most common would be a function which is supposed to return a new IDisposable. If the function isn't going to return, the disposable object must get cleaned up. Note that any "return" statements within the above "try" block must set ok to true.

In vb.net, it's possible to use a pattern which is functionally a little nicer, though one spot in the code is a little icky, with the pattern:

  Dim PendingException As Exception = Nothing;
  Try
    Do_Something
    PendingException = Nothing ' See note
  Catch Ex As Exception When CopyFirstParameterToSecondAndReturnFalse(Ex, PendingException )
    Throw ' Will never execute, since above will return false
  Finally
    If PendingException IsNot Nothing Then
      .. Handle exception
    EndIf
  End Try

The long-named function should be implemented in the obvious fashion. This pattern has the advantage of making the exception available to the code. While that isn't often needed in handle-but-don't-catch situations, there's one situation where it can be invaluable: if a cleanup routine throws an exception. Normally, if a cleanup routine throws an exception, any pending exception will be lost. With the above pattern, however, it's possible to wrap the pending exception within the cleanup exception.

One interesting note with the above code: it's possible for an exception to reach the "Catch When" and yet for the Try statement to complete normally. It's really not clear what should happen in that circumstance, but one thing that is clear is that the Finally statement should not act as though an exception is pending. Clearing PendingException will make it so that if an exception vanishes, the code will behave as though it never happened. An alternative would be to wrap and rethrow an exception which is known to have occurred, since that situation almost certainly indicates something wrong with the inner exception-handling code.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • My first thought was the same as your first example, but it's very difficult to imagine a case where you have an object that implements `IDisposable` and you couldn't just wrap the whole thing in a `using` block. There are several other questions here that demonstrate conclusively there's nothing wrong with returning from *inside* of a `using` block. As far as your second example, blech! It certainly seems "clever", but it's one of those things that's far *too* clever for one's own good. I can't imagine being forced to maintain code like that. Wrapping it as the inner ex is much clearer. – Cody Gray - on strike Mar 01 '11 at 23:36
  • @Cody Gray: How does one usefully wrap a constructor in a "using" block? If the object is going to go out of scope without being returned to the caller, it must be Dispose'd, but if the object is going to get returned to the caller, it must not be. A "using" block doesn't provide any assistance with such scenarios. – supercat Mar 02 '11 at 00:20
  • @Cody Gray: As for the icky code to capture the pending exception, I wish there were some better language construct, but there isn't. If an area of code that will require cleanup throws an exception, it's not uncommon that the condition that cause the exception will also cause cleanup to fail. It's bad to swallow exceptions from a failed cleanup, but it's also bad to have cleanup code throw a new exception that destroys an earlier one. Best is for the cleanup to throw an exception with the old exception as an InnerException, but *HOW DOES ONE DO THAT* without code similar to what I showed? – supercat Mar 02 '11 at 00:24