Reputation: 4577
A colleague found a wired debugger behavior in in his VB.net solution. I confess this will be more an academic question as this only effects the sequence of highlighted statements while debugging and not the overall behavior of the code. So for all curious out there:
We stripped down this to the following minimum console application:
Private Sub PlayWithExceptions
Dim a = 2
Try
throw new Exception("1")
Catch ex As Exception
If a = 2 Then
Dim x = New XElement("Dummy")
Else
throw
End If
End Try
End Sub
Sub Main()
Try
PlayWithExceptions()
Catch ex As Exception
End Try
End Sub
As obvious the debugger throws Exception(“1”) and the debugger jumps into the catch clause of PlayWithExceptions method. There, as “a” is always 2, the debugger jumps to some dummy code (New XElement…), from there to the “End If” and finally back into the Else-leaf onto the throw statement. I confess that Visual Studio does not re-throw the exception, but nevertheless it looks very strange.
Changing the condition “If a = 2” into “If True” eliminates this behavior.
Refactoring to conditional catches eliminates this behavior too.
Private Sub PlayWithExceptions
Dim a = 2
Try
throw new Exception("1")
Catch ex As Exception When a = 2
Dim x = New XElement("Dummy")
Catch ex As Exception
throw
End Try
End sub
Translating these few lines into C# does not show this behavior as well.
private static void PlayWithExceptions()
{
var a = 2;
try
{
throw new Exception("1");
}
catch (Exception)
{
if (a == 2)
{
var x = new XElement("Dummy");
}
else
{
throw;
}
}
}
static void Main(string[] args)
{
try
{
PlayWithExceptions();
}
catch (Exception ex)
{
}
}
We tried .Net3.5 and .Net4.6 as well as the targets AnyCPU and x86 without any effect to the above VB-code. The code was executed with the default Debug settings and no further optimizations. We used VS2015 Update 3.
Has anyone an idea why Visual Studio pretends to re-throw the exception in VB (but without really re-throwing it)? It looks confusing while debugging…
Upvotes: 4
Views: 194
Reputation: 239664
It's related to hidden code which sets/unsets error information for VB.Net's Err
object - which doesn't have a real "location" in the source.
In the IL, the code to clear the error is located immediately after the rethrow
call, and so that's the closest source line it can show when its about to invoke it. What I cannot answer is why it stops before invoking it when it should just be stepping between (visible) source lines.
But if you inspect the Err
object when the debugger is on the Throw
line, you'll see that it has a current exception object. Whereas on the step after that, the current exception has been cleared. See IL_0035
below for where the debugger is pausing:
.method private static void PlayWithExceptions() cil managed
{
// Code size 62 (0x3e)
.maxstack 2
.locals init ([0] int32 a,
[1] class [mscorlib]System.Exception ex,
[2] bool V_2,
[3] class [System.Xml.Linq]System.Xml.Linq.XElement x)
IL_0000: nop
IL_0001: ldc.i4.2
IL_0002: stloc.0
.try
{
IL_0003: nop
IL_0004: ldstr "1"
IL_0009: newobj instance void [mscorlib]System.Exception::.ctor(string)
IL_000e: throw
} // end .try
catch [mscorlib]System.Exception
{
IL_000f: dup
IL_0010: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::SetProjectError(class [mscorlib]System.Exception)
IL_0015: stloc.1
IL_0016: nop
IL_0017: ldloc.0
IL_0018: ldc.i4.2
IL_0019: ceq
IL_001b: stloc.2
IL_001c: ldloc.2
IL_001d: brfalse.s IL_0032
IL_001f: ldstr "Dummy"
IL_0024: call class [System.Xml.Linq]System.Xml.Linq.XName [System.Xml.Linq]System.Xml.Linq.XName::op_Implicit(string)
IL_0029: newobj instance void [System.Xml.Linq]System.Xml.Linq.XElement::.ctor(class [System.Xml.Linq]System.Xml.Linq.XName)
IL_002e: stloc.3
IL_002f: nop
IL_0030: br.s IL_0035
IL_0032: nop
IL_0033: rethrow
//Debugger is pausing at IL_0035 when the highlight is on Throw
IL_0035: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::ClearProjectError()
IL_003a: leave.s IL_003c
} // end handler
IL_003c: nop
IL_003d: ret
} // end of method Module1::PlayWithExceptions
For the If True
variant, it doesn't even include the Throw
code any more and so it obviously can never believe that it's about to execute it. For the variant with exception filters, each Catch
clause independently manages its SetProjectError
/ClearProjectError
calls and so there's no confusion between the one called for Throw
and the one called for New XElement
.
Upvotes: 5