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

Rob*_*ino 7 c# debugging visual-studio

为什么这会变成BOOM?

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

Ron*_*yer 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 
  {
Run Code Online (Sandbox Code Playgroud)

当您拖动执行光标时,您将面临损坏调用堆栈的风险。这是因为拖动光标实际上会跳过这些行。在调试器中运行时,按 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
Run Code Online (Sandbox Code Playgroud)

您可以看到例程中的第一行创建了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!");
        }
    }
Run Code Online (Sandbox Code Playgroud)

结果是工作代码。检查 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
  {
Run Code Online (Sandbox Code Playgroud)

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