35

I have this code

try
{
  //AN EXCEPTION IS GENERATED HERE!!!
}
catch  
{
   SqlService.RollbackTransaction();
   throw;
}

Code above is called in this code

try
{
  //HERE IS CALLED THE METHOD THAT CONTAINS THE CODE ABOVE
}
catch (Exception ex)
{
   HandleException(ex);
}

The exception passed as parameter to the method "HandleException" contains the line number of the "throw" line in the stack trace instead of the real line where the exception was generated. Anyone knows why this could be happening?

EDIT1 Ok, thanks to all for your answers. I changed the inner catch for


catch(Exception ex)
{
    SqlService.RollbackTransaction();
    throw new Exception("Enrollment error", ex);
}

Now I have the correct line on the stack trace, but I had to create a new exception. I was hoping to find a better solution :-(

EDIT2 Maybe (if you have 5 minutes) you could try this scenario in order to check if you get the same result, not very complicated to recreate.

Claudio Redi
  • 67,454
  • 15
  • 130
  • 155

6 Answers6

41

Yes, this is a limitation in the exception handling logic. If a method contains more than one throw statement that throws an exception then you'll get the line number of the last one that threw. This example code reproduces this behavior:

using System;

class Program {
    static void Main(string[] args) {
        try {
            Test();
        }
        catch (Exception ex) {
            Console.WriteLine(ex.ToString());
        }
        Console.ReadLine();
    }
    static void Test() {
        try {
            throw new Exception();  // Line 15
        }
        catch {
            throw;                  // Line 18
        }
    }
}

Output:

System.Exception: Exception of type 'System.Exception' was thrown.
   at Program.Test() in ConsoleApplication1\Program.cs:line 18
   at Program.Main(String[] args) in ConsoleApplication1\Program.cs:line 6

The work-around is simple, just use a helper method to run the code that might throw an exception.

Like this:

static void Test() {
    try {
        Test2();                // Line 15
    }
    catch {
        throw;                  // Line 18
    }
}
static void Test2() {
    throw new Exception();      // Line 22
}

The underlying reason for this awkward behavior is that .NET exception handling is built on top of the operating system support for exceptions. Called SEH, Structured Exception Handling in Windows. Which is stack-frame based, there can only be one active exception per stack frame. A .NET method has one stack frame, regardless of the number of scope blocks inside the method. By using the helper method, you automatically get another stack frame that can track its own exception. The jitter also automatically suppresses the inlining optimization when a method contains a throw statement so there is no need to explicitly use the [MethodImpl] attribute.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
13

"But throw; preserves the stack trace !! Use throw; "

How many times have you heard that... Well anyone who has been programming .NET for a while has almost certainly heard that and probably accepted it as the be all and end all of 'rethrowing' exceptions.

Unfortunately it's not always true. As @hans explains, if the code causing the exception occurs in the same method as the throw; statement then the stack trace gets reset to that line.

One solution is to extract the code inside the try, catch into a separate method, and another solution is to throw a new exception with the caught exception as an inner exception. A new method is slightly clumsy, and a new Exception() loses the original exception type if you attempt to catch it further up the call stack.

I found a better description of this problem was found on Fabrice Marguerie's blog.

BUT even better there's another StackOverflow question which has solutions (even if some of them involve reflection):

  In C#, how can I rethrow InnerException without losing stack trace?

Community
  • 1
  • 1
Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
12

As of .NET Framework 4.5 you can use the ExceptionDispatchInfo class to do this without the need for another method. For example, borrowing the code from Hans' excellent answer, when you just use throw, like this:

using System;

class Program {
    static void Main(string[] args) {
        try {
            Test();
        }
        catch (Exception ex) {
            Console.WriteLine(ex.ToString());
        }
        Console.ReadLine();
    }
    static void Test() {
        try {
            throw new ArgumentException();  // Line 15
        }
        catch {
            throw;                          // Line 18
        }
    }
}

It outputs this:

System.ArgumentException: Value does not fall within the expected range.
   at Program.Test() in Program.cs:line 18
   at Program.Main(String[] args) in Program.cs:line 6

But, you can use ExceptionDispatchInfo to capture and re-throw the exception, like this:

using System;

class Program {
    static void Main(string[] args) {
        try {
            Test();
        }
        catch (Exception ex) {
            Console.WriteLine(ex.ToString());
        }
        Console.ReadLine();
    }
    static void Test() {
        try {
            throw new ArgumentException();              // Line 15
        }
        catch(Exception ex) {
            ExceptionDispatchInfo.Capture(ex).Throw();  // Line 18
        }
    }
}

Then it will output this:

System.ArgumentException: Value does not fall within the expected range.
   at Program.Test() in Program.cs:line 15
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Program.Test() in Program.cs:line 18
   at Program.Main(String[] args) in Program.cs:line 6

As you can see, ExceptionDispatchInfo.Throw appends additional information to the stack trace of the original exception, adding the fact that it was re-thrown, but it retains the original line number and exception type. See the MSDN documentation for more information.

Steven Doggart
  • 43,358
  • 8
  • 68
  • 105
5

Does the date/time stamp of your .pdb file match your .exe/.dll file? If not, it could be that the compilation is not in "debug mode" which generates a fresh .pdb file on each build. The pdb file has the accurate line numbers when exceptions occur.

Look into your compile settings to make sure the debug data is generated, or if you're in a test/production environment, check the .pdb file to make sure the timestamps match.

Dillie-O
  • 29,277
  • 14
  • 101
  • 140
4

C# stack traces are generated at throw time, not at exception creation time.

This is different from Java, where the stack traces are filled at exception creation time.

This is apparently by design.

Randolpho
  • 55,384
  • 17
  • 145
  • 179
  • 1
    Yes, but `throw;` preserves the original stack trace. – SLaks Mar 22 '10 at 16:17
  • @SLaks: Excellent point. Based on @Andy Shellam's comment to the original question, I'm starting to think maybe the original exception is getting swallowed by an exception in the catch block. – Randolpho Mar 22 '10 at 16:19
3

I often get this in production systems if Optimize code is checked. This screws up line numbers even in 2016.

Make sure your configuration is set to 'Release' or whatever configuration you are building and deploying under. The checkbox has a different value per configuration

I never ultimately know how more 'optimized' my code is with this checked - so check it back if you need to - but it has saved my stack trace on many occasions.

enter image description here

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689