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

标签 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 不会重新抛出异常,但它看起来很奇怪。

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

将这几行翻译成 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 代码没有任何影响。代码是使用默认调试设置执行的,没有进一步优化。我们使用 VS2015 Update 3。

有谁知道为什么 Visual Studio 假装在 VB 中重新抛出异常(但实际上并没有重新抛出它)?调试时看起来很困惑……

最佳答案

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

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

但是如果当调试器在 Throw 行时检查 Err 对象,您会看到它有一个当前异常对象。而在那之后的步骤中,当前异常已被清除。请参阅下面的 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 之间。

关于c# - VB.net 中奇怪的调试器行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40108593/

相关文章:

asp.net - .Net AJAX 中是否有内置方式将对象手动序列化为 JSON 字符串?

objective-c - 为什么我的 Cocoa 选择器实际上没有被调用?

c# - Owin 仅提供特定文件夹中的文件

c# - Windows 服务总线 LockDuration 属性的值

c# - 输出行号

MySql 异常未处理 - 命令执行期间遇到 fatal error

c++ - 调试 Visual C++ 复杂项目

c# - Visual Studio 中是否有击键来切换中断所有 CLR 异常,第一次机会?

C# .NET - 以最小延迟发送大量小消息(通过网络)

c# - 如何知道 Linq To SQL 中的字段是否为数字