VB.net中奇怪的调试器行为

Mar*_*kus 4 c# vb.net debugging exception

一位同事在他的VB.net解决方案中发现了有线调试器行为.我承认这将是一个学术问题,因为这只会影响调试时突出显示的语句序列,而不会影响代码的整体行为.所以对于所有好奇的人:

我们将其拆除为以下最小控制台应用程序:

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
Run Code Online (Sandbox Code Playgroud)

很明显,调试器会抛出异常("1"),调试器会跳转到PlayWithExceptions方法的catch子句中.在那里,由于"a"始终为2,调试器跳转到一些虚拟代码(New XElement ...),从那里跳到"End If",最后返回到Else-leaf到throw语句.我承认Visual Studio不会重新抛出异常,但它看起来很奇怪.

将条件"If a = 2"更改为"If True"会消除此行为.

重构为条件捕获也消除了这种行为.

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
Run Code Online (Sandbox Code Playgroud)

将这几行转换为C#也不会显示此行为.

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)
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

我们尝试了.Net3.5和.Net4.6以及目标AnyCPU和x86,对上述VB代码没有任何影响.代码是使用默认的Debug设置执行的,没有进一步的优化.我们使用了VS2015 Update 3.

有没有人知道为什么Visual Studio假装在VB中重新抛出异常(但没有真正重新抛出它)?调试时看起来很混乱......

Dam*_*ver 5

它与隐藏代码有关,它为VB.Net的Err对象设置/取消设置错误信息- 它在源中没有真正的"位置".

在IL中,清除错误的代码位于rethrow调用之后,因此它是即将调用它时可以显示的最近源代码行.我无法回答的是,为什么它在调用它之前停止它应该只是在(可见)源行之间踩踏.

但是如果你Err在调试器上Throw线时检查对象,你会发现它有一个当前的异常对象.而在此之后的步骤中,当前异常已被清除.请参阅IL_0035下面的调试器暂停的位置:

.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
Run Code Online (Sandbox Code Playgroud)

对于If True变体,它甚至不再包含Throw代码,因此它显然永远不会相信它将要执行它.对于具有异常过滤器的变体,每个 Catch子句独立地管理它SetProjectError/ ClearProjectError调用,因此在被调用者Throw和被调用者之间没有混淆New XElement.