One of the ways I explore the framework is to look under the covers at what the compilers generate to see how the map to CLR concepts.  ILDASM is a great tool for this.  Here a few interesting examples I thought I’d share.  I ILDASM’ed a C# program then translated (roughly) the IL back into C# for your reading pleasure.

 

You write:

public enum Compression  {

   Zip, SuperZip, None

 }

The C# compiler generates:

public struct Compression : Enum {

   public int value__;

   public const Compression Zip = 0;

   public const Compression SuperZip = 1;

   public const Compression None = 2;

}

What can we learn:

Well, I think we can learn a couple of things. 

1)       Enums are all structs. All rule that applies to structs will apply to enums as well

2)       Enums derive from System.Enum --- wow, structs can have base class other than ValueType.

3)       The underlying type of enums by default is int (could be any integer type)

4)       Names with “__” must be reserved for use by the compiler.

5)       The values are all  consts… which means that are burned into the call site – you can’t ever change them without recompiling all the clients

6)       The values are all auto numbered for you based on the order in the source.  Better not reorder them in the source, see #5.

7)       All the values are of type Compression, this always the strong type checking in the compiler.

 

 

You write:

public class Resource

{

   ~Resource() {

      ...

   }

}

The C# compiler generates:

public class Resource

{

   protected override void Finalize() {

      try {

         ...

      }

      finally {

         base.Finalize();

      }

   }

}

What can we learn:

1)       destructor syntax (The ~) is nothing more than an override of the finalize method off object.  As such we know it is not deterministic.

2)       The implementations of this method (represented by the “…”) is wrapped in a try..finaly to be sure the base classes finalize method always get a chance to run even if you throw an exception.

 

 

You write:

using (Resource res = new Resource()) {

   res.DoWork();

}

The C# compiler generates:

Resource res = new Resource(...);

try {

   res.DoWork();

}

finally {

   if (res != null)
      ((IDisposable)res).Dispose();

}

What can we learn:

Res.Dispose() is guaranteed to get called when the using statement is exited, normally of via an exception

 

 

You write:

object x = ... ;

lock (x) {

   // critical section

}

The C# compiler generates:

System.Threading.Monitor.Enter(x);
try {
   // critical section
}
finally {
   System.Threading.Monitor.Exit(x);
}

What can we learn:

1)       Lock just uses Monitor.Enter and Exit… reading those docs give you a good idea of what is really happening here.

2)       We see our friend try..finally again. This time ensuring that the monitor is exited even if an exception is thrown in the critical section