为什么会出现这种情况?
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!");
}
}
}
}
答案 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) 看看下面的图像
我的集会非常糟糕,所以我不会试图假装理解所有正在发生的事情。
从这里开始
正常执行
我已经突出显示了您的数组为null的更改但它存在,您也可以看到注册表更改。
不要看看我拖动时会发生什么
你的变量甚至不存在(而且几乎看看注册表的变化),所以我猜你刚刚跳过几行或汇编。
现在你也可以看一下内存,看看那里什么也没有。
当我步入低谷时的记忆
空的空间,但它就在那里。
如果我步入其中甚至有值
拖动时的内存
没有。
考虑到我尝试使用2个版本的.NET和2个不同的编译器,我认为它必须是VisualStudio问题,您可以发布此错误here它可能会在将来的更新中修补。