在Visual Studio中更改下一行执行时出现意外后果

时间:2015-06-12 17:03:12

标签: c# visual-studio debugging

为什么会出现这种情况?

using System;
using System.Linq;

namespace Test
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            try
            {
                // 1. Hit F10 to step into debugging.
                string[] one = {"1"}; //2. Drag arrow to make this next statement executed
                // 3. Hit f5.
                Enumerable.Range(1,1)
                    .Where(x => one.Contains(x.ToString()));
            }
            catch (Exception exception)
            {
                Console.Write("BOOM!");
            }
        }
    }
}

2 个答案:

答案 0 :(得分:3)

查看ILDASM输出,这里可能有一个解释......

  .locals init ([0] class Test.Program/'<>c__DisplayClass1' 'CS$<>8__locals2',
           [1] class [mscorlib]System.Exception exception,
           [2] string[] CS$0$0000)
  IL_0000:  nop
  .try
  {
    IL_0001:  newobj     instance void Test.Program/'<>c__DisplayClass1'::.ctor()
    IL_0006:  stloc.0
    IL_0007:  nop
    IL_0008:  ldloc.0
    IL_0009:  ldc.i4.1
    IL_000a:  newarr     [mscorlib]System.String
    IL_000f:  stloc.2
    IL_0010:  ldloc.2
    IL_0011:  ldc.i4.0
    IL_0012:  ldstr      "1"
    IL_0017:  stelem.ref
    IL_0018:  ldloc.2
    IL_0019:  stfld      string[] Test.Program/'<>c__DisplayClass1'::one
    IL_001e:  ldc.i4.1
    IL_001f:  ldc.i4.1
    IL_0020:  call       class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32,
                                                                                                                                    int32)
    IL_0025:  ldloc.0
    IL_0026:  ldftn      instance bool Test.Program/'<>c__DisplayClass1'::'<Main>b__0'(int32)
    IL_002c:  newobj     instance void class [mscorlib]System.Func`2<int32,bool>::.ctor(object,
                                                                                        native int)
    IL_0031:  call       class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0> [System.Core]System.Linq.Enumerable::Where<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>,
                                                                                                                                         class [mscorlib]System.Func`2<!!0,bool>)
    IL_0036:  pop
    IL_0037:  nop
    IL_0038:  leave.s    IL_004a
  }  // end .try
  catch [mscorlib]System.Exception 
  {

拖动执行光标时,存在损坏调用堆栈的风险。这是因为拖动光标会逐字地跳过这些行。在调试器中运行时,在点击F10后,光标在尝试之前停在Main例程的开头。如果您将光标拖动到数组的创建,您将跳过这个有趣的行:

IL_0001: newobj instance void Test.Program/'<>c__DisplayClass1'::.ctor()

创建Program类的实例。然后在这里使用程序类:

IL_0019: stfld string[] Test.Program/'<>c__DisplayClass1'::one

因为您跳过它,没有创建该对象,所以在运行时会得到NullReferenceException

为什么人们无法在VS2012上重现这一点,我不确定。也许编译器输出不同的IL,但这是我可以想出使用VS2013 Ultimate和C#4.5。

有趣的是,当你注释掉try / catch时,IL中程序的开头是这样的:

.locals init ([0] class Test.Program/'<>c__DisplayClass1' 'CS$<>8__locals2',
           [1] string[] CS$0$0000)
  IL_0000:  newobj     instance void Test.Program/'<>c__DisplayClass1'::.ctor()
  IL_0005:  stloc.0

您可以在其中看到例程中的第一行创建Program对象。为什么编译器决定将该行放在try / catch中,这超出了我的范围。

修改

深入挖掘,将程序更改为:

    private static void Main(string[] args)
    {
        string[] one;

        try
        {
            // 1. Hit F10 to step into debugging.
            one = new string[] { "1" }; //2. Drag arrow to this
            // 3. Hit f5.
            Enumerable.Range(1, 1)
                .Where(x => one.Contains(x.ToString()));
        }
        catch (Exception exception)
        {
            Console.Write("BOOM!");
        }
    }

工作代码的结果。检查IL,您可以看到实例创建已移到try:

之外
  .locals init ([0] class [mscorlib]System.Exception exception,
           [1] class [mscorlib]System.Func`2<int32,bool> 'CS$<>9__CachedAnonymousMethodDelegate1',
           [2] class Test.Program/'<>c__DisplayClass2' 'CS$<>8__locals3',
           [3] string[] CS$0$0000)
  IL_0000:  ldnull
  IL_0001:  stloc.1
  IL_0002:  newobj     instance void Test.Program/'<>c__DisplayClass2'::.ctor()
  IL_0007:  stloc.2
  IL_0008:  nop
  .try
  {

编译器足以将字符串数组的创建从try外部移动到try内部,因此跳过该行仍会产生有效对象。代码有效,所以我猜测NullReferenceException确实是Program类的实例。

答案 1 :(得分:1)

长话短说,当你拖动箭头时,one变量没有内存分配,所以基本上我认为你在一个漂亮的包装器中得到一个Null Pointer Reference

有趣的是,在.NET4.0,NET4.5,VS2013和VS2015 RC中会发生同样的事情,它应该有不同的编译器(Roslyn) 看看下面的图像

我的集会非常糟糕,所以我不会试图假装理解所有正在发生的事情。

从这里开始

enter image description here

正常执行

enter image description here

我已经突出显示了您的数组为null的更改但它存在,您也可以看到注册表更改。

不要看看我拖动时会发生什么

enter image description here

你的变量甚至不存在(而且几乎看看注册表的变化),所以我猜你刚刚跳过几行或汇编。

现在你也可以看一下内存,看看那里什么也没有。

当我步入低谷时的记忆

enter image description here

空的空间,但它就在那里。

如果我步入其中甚至有值

enter image description here

拖动时的内存

enter image description here

没有。

考虑到我尝试使用2个版本的.NET和2个不同的编译器,我认为它必须是VisualStudio问题,您可以发布此错误here它可能会在将来的更新中修补。