I'm flabbergasted. I always thought that throw
by itself in a catch block would throw the exception at hand without altering the stack trace, but that throw ex
in a catch block would alter the stack trace to show the exception originating at the location of the statement.
Take the following two blocks of code. I would expect the output to be subtly different because one uses throw
and the other uses throw ex
, but the output is identical between the two, and the actual source line that incited the initial exception is lost in both cases, which seems terrible to me. What am I missing?
This first example behaves as I would expect:
using System;
public class Program
{
public static void Main()
{
try
{
DummyWork();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
private static void DummyWork()
{
try
{
throw new Exception("dummy");
}
catch (Exception ex)
{
Console.WriteLine(ex);
throw ex; // I would expect to lose the information about the inciting line 5 above this one in this case.... and I do.
}
}
}
This second example behaves identical to the first, but I DO NOT expect that:
using System;
public class Program
{
public static void Main()
{
try
{
DummyWork();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
private static void DummyWork()
{
try
{
throw new Exception("dummy");
}
catch (Exception ex)
{
Console.WriteLine(ex);
throw; // I would NOT expect to lose the information about the inciting line 5 above this one in this case.... But I do. Output is identical.
}
}
}
UPDATE: Some commenters have said they can't repro this - here is my dot fiddle (you will have to manually edit it to go back and forth between the two versions): https://dotnetfiddle.net/Mj7eK5
UPDATE #2: In answer to some commenters who have asked for the "identical" output. Here is the output from the first example:
System.Exception: dummy
at Program.DummyWork() in d:\Windows\Temp\xoyupngb.0.cs:line 21
System.Exception: dummy
at Program.DummyWork() in d:\Windows\Temp\xoyupngb.0.cs:line 26
at Program.Main() in d:\Windows\Temp\xoyupngb.0.cs:line 9
And here is the output from the second example:
System.Exception: dummy
at Program.DummyWork() in d:\Windows\Temp\jy4xgqrf.0.cs:line 21
System.Exception: dummy
at Program.DummyWork() in d:\Windows\Temp\jy4xgqrf.0.cs:line 26
at Program.Main() in d:\Windows\Temp\jy4xgqrf.0.cs:line 9
Leaving aside the insignificant temp file differences, in both cases the outer catch (the second one) is missing the line 21 of the initial throw. I would expect that in the first example throw ex
but not in the second throw
.
CodePudding user response:
Note: This answer is for .NET Framework. You might observe a different behavior if you're using .NET Core or .NET 5.0 and above, as mentioned in the comments. I did not test on all versions of .NET Core
Okay, let me take a stab at it. The difference between throw
and throw ex
is already explained in Is there a difference between "throw" and "throw ex"? but I'll try to put it in more clear terms to fit the narrative of this question.
throw ex
: Rethrows the exception from this point and resets the stack trace.throw
: Rethrows the exception from this point and preserves the stack trace.
Let's look at the code in question:
private static void DummyWork()
{
try
{
throw new Exception("dummy"); // Line 21
}
catch (Exception ex)
{
throw; // Line 25
}
}
Here, whether we use throw
or throw ex
, the stack trace will always be:
at Program.DummyWork() in ...:line 25
at Program.Main() in ...:line 9
Q: Why "line 25"?
A: Because both throw
and throw ex
rethrow the exception from that point.
Q: Why is there no difference in this case?
A: Because there aren't any more stack frames in the stack trace to reset.
Q: How can we see the difference?
Well, let's add another level to generate another stack frame. The code would be something like this:
private static void DummyWork()
{
try
{
MoreDummyWork(); // Line 21
}
catch (Exception ex)
{
throw; //Line 25
}
}
private static void MoreDummyWork()
{
throw new Exception("dummy"); // Line 31
}
Here, we can clearly see the difference. If we use throw
, the stack trace is the following:
at Program.MoreDummyWork() in ...:line 31
at Program.DummyWork() in ...:line 25
at Program.Main() in ...:line 9
But if we use throw ex
, the stack trace becomes:
at Program.DummyWork() in ...:line 25
at Program.Main() in ...:line 9
Q: Okay, you say both will throw the exception from that point. What if I want to maintain the original line number?
A: In this case, you can use ExceptionDispatchInfo.Capture(ex).Throw();
as explained in How to rethrow InnerException without losing stack trace in C#?: