Why does this go 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!");
}
}
}
}
Looking at the ILDASM output, there might be an explanation here...
.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
{
When you drag the execution cursor, you run the risk of corrupting the call stack. This is because dragging the cursor literally skips those lines. When running in the debugger, after hitting F10, the cursor stops at the start of the Main
routine, before the try. If you drag the cursor to the creation of the array, you are skipping this fun line:
IL_0001: newobj instance void Test.Program/'<>c__DisplayClass1'::.ctor()
Which creates an instance of the Program
class. The program class is then used later here:
IL_0019: stfld string[] Test.Program/'<>c__DisplayClass1'::one
Which because you skipped it, didn't create that object, so you get a NullReferenceException
when running.
Why people can't reproduce this on VS2012, I'm not sure. Maybe the compiler is outputting different IL, but this is far as I can come up with using VS2013 Ultimate and C#4.5.
Interestingly enough, when you comment out the try/catch, the start of the program in IL looks like this:
.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
Which you can see the first line in the routine creates the Program
object. Why the compiler decided to put that line inside the try/catch is beyond me.
EDIT
Digging a little deeper, changing your program to this:
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!");
}
}
Results in working code. Examining the IL, you can see that the instance creation was moved outside the 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
{
The compiler was nice enough to move the creation of the string array from outside the try to inside the try, so skipping that line still results in a valid object. The code works, so I'm guessing that the NullReferenceException
really is the instance of the Program
class.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With