I tried to take a look at the disassembly of the code posted in this old question, and I found something odd.
Here's the source code, for the sake of clarity:
class ThreadTest
{
static void Main(string[] args)
{
for (int i = 0; i < 10; i++)
new Thread(() => Console.WriteLine(i)).Start();
}
}
(Of course the behaviour of this program is unexpected, that is not the question here.)
Here's what I saw looking at the disassembly:
internal class ThreadTest
{
private static void Main(string[] args)
{
int i;
int j;
for (i = 0; i < 10; i = j + 1)
{
new Thread(delegate
{
Console.WriteLine(i);
}).Start();
j = i;
}
}
}
What is j
doing there? Here's the bytecode:
.method private hidebysig static
void Main (
string[] args
) cil managed
{
// Method begins at RVA 0x2050
// Code size 64 (0x40)
.maxstack 2
.entrypoint
.locals init (
[0] class ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0' 'CS$<>8__locals0',
[1] int32
)
IL_0000: newobj instance void ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::.ctor()
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: ldc.i4.0
IL_0008: stfld int32 ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::i
IL_000d: br.s IL_0035
// loop start (head: IL_0035)
IL_000f: ldloc.0
IL_0010: ldftn instance void ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::'<Main>b__0'()
IL_0016: newobj instance void [mscorlib]System.Threading.ThreadStart::.ctor(object, native int)
IL_001b: newobj instance void [mscorlib]System.Threading.Thread::.ctor(class [mscorlib]System.Threading.ThreadStart)
IL_0020: call instance void [mscorlib]System.Threading.Thread::Start()
IL_0025: ldloc.0
IL_0026: ldfld int32 ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::i
IL_002b: ldc.i4.1
IL_002c: add
IL_002d: stloc.1
IL_002e: ldloc.0
IL_002f: ldloc.1
IL_0030: stfld int32 ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::i
IL_0035: ldloc.0
IL_0036: ldfld int32 ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::i
IL_003b: ldc.i4.s 10
IL_003d: blt.s IL_000f
// end loop
IL_003f: ret
} // end of method ThreadTest::Main
But here's the weirdest thing. If I change the original code like this, replacing i++
with i = i + 1
:
class ThreadTest
{
static void Main(string[] args)
{
for (int i = 0; i < 10; i = i + 1)
new Thread(() => Console.WriteLine(i)).Start();
}
}
I get this:
internal class ThreadTest
{
private static void Main(string[] args)
{
int i;
for (i = 0; i < 10; i++)
{
new Thread(delegate
{
Console.WriteLine(i);
}).Start();
}
}
}
Which is exactly what I expected.
Here's the bytecode:
.method private hidebysig static
void Main (
string[] args
) cil managed
{
// Method begins at RVA 0x2050
// Code size 62 (0x3e)
.maxstack 3
.entrypoint
.locals init (
[0] class ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0' 'CS$<>8__locals0'
)
IL_0000: newobj instance void ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::.ctor()
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: ldc.i4.0
IL_0008: stfld int32 ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::i
IL_000d: br.s IL_0033
// loop start (head: IL_0033)
IL_000f: ldloc.0
IL_0010: ldftn instance void ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::'<Main>b__0'()
IL_0016: newobj instance void [mscorlib]System.Threading.ThreadStart::.ctor(object, native int)
IL_001b: newobj instance void [mscorlib]System.Threading.Thread::.ctor(class [mscorlib]System.Threading.ThreadStart)
IL_0020: call instance void [mscorlib]System.Threading.Thread::Start()
IL_0025: ldloc.0
IL_0026: ldloc.0
IL_0027: ldfld int32 ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::i
IL_002c: ldc.i4.1
IL_002d: add
IL_002e: stfld int32 ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::i
IL_0033: ldloc.0
IL_0034: ldfld int32 ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::i
IL_0039: ldc.i4.s 10
IL_003b: blt.s IL_000f
// end loop
IL_003d: ret
} // end of method ThreadTest::Main
Why did the compiler add j
in the first scenario?
Note: I'm using VS 2015 Update 3, .NET Framework 4.5.2, compiling in Release mode.