When I compile the following code as debug...
public class MyClass
{
private int myField;
public int MyProperty
{
get { return myField; }
set { myField = value; }
}
}
...strange bytecode with seemingly useless instructions is produced by the compiler. For instance see what is generated for the getter of property MyProperty
(disassembled with ildasm.exe
):
.method public hidebysig specialname instance int32
get_MyProperty() cil managed
{
// Code size 12 (0xc)
.maxstack 1
.locals init ([0] int32 CS$1$0000)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldfld int32 MSILTest.MyClass::myField
IL_0007: stloc.0
IL_0008: br.s IL_000a
IL_000a: ldloc.0
IL_000b: ret
} // end of method MyClass::get_MyProperty
Specifically, what is the nop
at IL_0000
doing there? And why does the compiler generate this useless br.s
instruction at IL_0008
literally out of nowhere? Why does it create the temporary local variable CS$1$0000
?
For a release configuration the instruction set is generated as expected though:
IL_0000: ldarg.0
IL_0001: ldfld int32 MSILTest.MyClass::myField
IL_0006: ret
EDIT
I think I've found an answer to the question why the branch and the temporary local variable is there in a different question: it's probably to easy setting break points during debugging. So the question that remains is why the nop
instruction is genereated.
The compiler creates these opcodes to provide a better debugging experience. At the start of each of your c# lines the compiler will introduce a
nop
so the debugger can break there. The branch and the temporary local variable are emitted to let you analyse the functions return value.