I already mentioned why every .NET dev should learn MSIL.  So now it's time to get down and dirty.  My goal is to be able to effectively use Reflection.Emit.  With that in mind I'd like for my emitted IL to work and perform similarly to compiler created IL.  I think the best approach to accomplishing this is by using the compiler as a reference.  I'll start with writing simple programs in C#(or VB.NET, etc) and then taking a peep at the MSIL under the hood.

Here's a simple Hello World.

class Program
{
    static void Main(string[] args)
    {
        Console.Write("Hello World");
    }
}

Microsoft provides a nice tool for looking at IL in the windows SDK called IL Disassembler (or ILDASM for short).  After opening up the compiled HelloWord exe in ILDASM.  I get this little bit of IL.

.class private auto ansi beforefieldinit ILTests.Program
       extends [mscorlib]System.Object
{
  .method private hidebysig static void  Main(string[] args) cil managed
  {
    .entrypoint
    // Code size       13 (0xd)
    .maxstack  8
    IL_0000:  nop
    IL_0001:  ldstr      "Hello World"
    IL_0006:  call       void [mscorlib]System.Console::Write(string)
    IL_000b:  nop
    IL_000c:  ret
  } // end of method Program::Main

  .method public hidebysig specialname rtspecialname
          instance void  .ctor() cil managed
  {
    // Code size       7 (0x7)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  ret
  } // end of method Program::.ctor

} // end of class ILTests.Program

Not too terribly different but we are going to have to do some digging to figure out exactly what's going on.  We can recognize some familiar constructs right away.  There is a private class called Program that inherits from Object (which resides in mscorlib).  We have a private static method called Main and what looks like a public constructor.

Let's take an in depth look at the class declaration:

.class private auto ansi beforefieldinit ILTests.Program
       extends [mscorlib]System.Object

There are a few keywords here that are unfamiliar.  Auto, ansi, and beforefieldinit in particular.  From my research, it seems auto is used by the CLI (Common Language Infrastructure) Loader.  It allows the loader to layout the class in anyway it sees fit.  The other layout options are sequential and explicit.  Beforefieldinit tells the CLI that it doesn't need to initialize the type before calling static methods. Ansi tells the CLI to marshall to and from Ansi strings. 

The method declaration looks like:

.method private hidebysig static void  Main(string[] args) cil managed

Again, breaking down the keywords that are likely unfamiliar:

hidebysig: Tells the CLI to hide any methods in the base class that match the signature of the method.

cil:  Tells the CLI that the method contains normal CIL (Common Intermediate Language) code (the same as MSIL). Other options include: native and runtime.

managed:  Lets the CLI know that the method is managed.  Other options: unmanaged.

Now looking at the body of the method:

      .entrypoint
    // Code size       13 (0xd)
    .maxstack  8
    IL_0000:  nop
    IL_0001:  ldstr      "Hello World"
    IL_0006:  call       void [mscorlib]System.Console::Write(string)
    IL_000b:  nop
    IL_000c:  ret

Things start to get interesting here. 

entrypoint: an ILASM keyword that designates a method as the entry point of the assembly.

maxstack: the number of stack slots required by the method.

nop: short for no operation.  It does nothing.

ldstr: short for loadstring.  Pushes a string onto the stack.

call: Invokes a method.

ret: short for return.

The first thing you'll notice is that IL is stack-based.  We push "Hello World" onto the stack.  Then we invoke System.Console.Write, passing "Hello World" as the argument.  Last, it returns out of the main method.

You probably have noticed that there was no constructor in the original C# code.  We know from general .NET knowledge that even if we do not explicitly define a constructor it can still be called.  That's because the compiler creates the constructor for us.

Take a look at the constructor's code:

.method public hidebysig specialname rtspecialname
          instance void  .ctor() cil managed

specialname: indicates that the name of this item can have special significance to tools other than the CLI.

rtspecialname: indicates that the name of this item has special significance to the CLI.  Any item marked rtspecialname shall also be marked specialname.

The contructor code is seems pretty straight forward now:

// Code size       7 (0x7)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  ret

ldarg.n: loads argument n onto the stack.  In this case ldarg.0 actually points to "this" (as is the case with instance methods).

The constructor loads its instance onto the stack and then calls the base class constructor.

So that's your basic Hello World app in IL.