Throwing exceptions - Why is my stack trace lost?You might have read, that re-throwing an exception like this: throw exc; is considered bad practice and you should just do this: throw; instead. But why is it like that? For that we have to discuss some core principles about exception handling. Let's see a small example here: try {
Throws();
}
catch (Exception exc)
{
Console.WriteLine("ohoh an exception");
throw;
}
void Throws() => throw new Exception("??");
This will output something along the like this: ohoh an exception
Exception
System.Exception: ??
at Program.<<Main>$>g__Throws|0_0()
at Program.<Main>$(String[] args)
Now let's do the same, but we are "re-throwing" the exception object itself: try {
Throws();
}
catch (Exception exc)
{
Console.WriteLine("ohoh an exception");
throw exc;
}
void Throws() => throw new Exception("??");
The output: ohoh an exception
Exception
System.Exception: ??
at Program.<Main>$(String[] args)
Wait a second? There is something missing! Our Throw method is not in the stack-trace anymore! And that is exactly the difference when you re-throw like that. You will cut off the stack-trace until the catch block where you re-throw the object. Now there are valid cases where you really want this, mainly because of security concerns. I will not discuss this here in more detail. Why does that happen?. To answer this you have to understand one important thing: Exceptions are not immutable! Let's have a look at the IL-code for this simplified version (and ignore the fact that the code itself is pretty dumb): try {
throw new Exception("??");
}
catch (Exception exc)
{
throw exc;
}
catch
{
throw;
}
will be translated to: .try
{
IL_0000: nop
IL_0001: ldstr "\ud83d\udca3"
IL_0006: newobj instance void [System.Runtime]System.Exception::.ctor(string)
IL_000b: throw
}
catch [System.Runtime]System.Exception
{
IL_000c: stloc.0
IL_000d: nop
IL_000e: ldloc.0
IL_000f: throw
}
catch [System.Runtime]System.Object
{
IL_0010: pop
IL_0011: nop
IL_0012: rethrow
}
Two things are important here: The throw new Exception and throw exc; do have the same IL code. Only throw; has a different IL-Code: rethrow . Here is where this mutability part comes into play. You as a developer, you don't have to set the stack-trace at all. So someone else has to do it and you can guess who is doing that: the throw keyword does that for you. So yes that is all the magic. Rethrowing with the original stack traceI told you earlier that there are valid use-cases to handle an exception and rethrow it later. If you still want to have the whole stack trace there is a helper class for you: ExceptionDispatchInfo . It offers a Throw method, which re-throws the saved exception with the given stack-trace. using System.Runtime.ExceptionServices;
ExceptionDispatchInfo? exceptionDispatchInfo;
try
{
Throws();
}
catch (Exception exc)
{
exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exc);
}
if (exceptionDispatchInfo is not null)
exceptionDispatchInfo.Throw();
void Throws() => throw new Exception("??");
Produces the following output: Exception
System.Exception: ??
at Program.<<Main>$>g__Throws|0_0()
at Program.<Main>$(String[] args)
--- End of stack trace from previous location ---
at Program.<Main>$(String[] args)
|