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

时间:2016-10-18 12:41:51

标签: 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

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

将条件“如果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

将这几行转换为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)
    {
    }
}

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

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

1 个答案:

答案 0 :(得分: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

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